<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Best Practices on Posit Open Source</title>
    <link>https://posit-open-source.netlify.app/categories/best-practices/</link>
    <description>Recent content in Best Practices on Posit Open Source</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 18 Feb 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://posit-open-source.netlify.app/categories/best-practices/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Rapp 0.3.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/rapp-0-3-0/</link>
      <pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/rapp-0-3-0/</guid>
      <dc:creator>Tomasz Kalinowski</dc:creator><description><![CDATA[<p>We&rsquo;re excited to share our first tidyverse blog post for Rapp, alongside the <code>0.3.0</code> release. Rapp helps you turn R scripts into polished command-line tools, with argument parsing and help generation built in.</p>
<h2 id="why-a-command-line-interface-for-r">Why a command-line interface for R?
</h2>
<p>A command-line interface (CLI) lets you run programs from a terminal, without opening an IDE or starting an interactive R session. This is useful when you want to:</p>
<ul>
<li>automate tasks via cron jobs, scheduled tasks, or CI/CD pipelines</li>
<li>chain R scripts together with other tools in data pipelines</li>
<li>let others run your R code without needing to know R</li>
<li>package reusable tools that feel native to the terminal</li>
<li>expose specific actions through a clean interface that LLM agents can invoke</li>
</ul>
<p>There are several established packages for building CLIs in R, including argparse, optparse, and docopt, where you explicitly parse and handle command-line arguments in code. Rapp takes a different approach: it derives the CLI surface from the structure of your R script and injects values at runtime, so you never need to handle CLI arguments manually.</p>
<h2 id="how-rapp-works">How Rapp works
</h2>
<p>At its core, Rapp is an alternative front-end to R: a drop-in replacement for <code>Rscript</code> that automatically turns common R expression patterns into command-line options, switches, positional arguments, and subcommands. You write normal R code and Rapp handles the CLI surface.</p>
<p>Rapp also uses special <code>#|</code> comments (similar to Quarto&rsquo;s YAML-in-comments syntax) to add metadata such as help descriptions and short aliases.</p>
<h2 id="a-tiny-example">A tiny example
</h2>
<p>Here&rsquo;s a complete Rapp script (from the package examples), a coin flipper:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env Rapp</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| name: flip-coin</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| description: |</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   Flip a coin.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#| description: Number of coin flips</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| short: &#39;n&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">flips</span> <span class="o">&lt;-</span> <span class="m">1L</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sep</span> <span class="o">&lt;-</span> <span class="s">&#34; &#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">wrap</span> <span class="o">&lt;-</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">seed</span> <span class="o">&lt;-</span> <span class="kc">NA_integer_</span>
</span></span><span class="line"><span class="cl"><span class="kr">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">is.na</span><span class="p">(</span><span class="n">seed</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">set.seed</span><span class="p">(</span><span class="n">seed</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">cat</span><span class="p">(</span><span class="nf">sample</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;heads&#34;</span><span class="p">,</span> <span class="s">&#34;tails&#34;</span><span class="p">),</span> <span class="n">flips</span><span class="p">,</span> <span class="kc">TRUE</span><span class="p">),</span> <span class="n">sep</span> <span class="o">=</span> <span class="n">sep</span><span class="p">,</span> <span class="n">fill</span> <span class="o">=</span> <span class="n">wrap</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Let&rsquo;s break down how Rapp interprets this script:</p>
<table>
  <thead>
      <tr>
          <th>R code</th>
          <th>Generated CLI option</th>
          <th>What it does</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>flips &lt;- 1L</code></td>
          <td><code>--flips</code> or <code>-n</code></td>
          <td>Integer option with default of 1</td>
      </tr>
      <tr>
          <td><code>sep &lt;- &quot; &quot;</code></td>
          <td><code>--sep</code></td>
          <td>String option with default of <code>&quot; &quot;</code></td>
      </tr>
      <tr>
          <td><code>wrap &lt;- TRUE</code></td>
          <td><code>--wrap</code> / <code>--no-wrap</code></td>
          <td>Boolean toggle (TRUE/FALSE becomes on/off)</td>
      </tr>
      <tr>
          <td><code>seed &lt;- NA_integer_</code></td>
          <td><code>--seed</code></td>
          <td>Optional integer (NA means &ldquo;not set&rdquo;)</td>
      </tr>
  </tbody>
</table>
<p>The <code>#| short: 'n'</code> comment adds <code>-n</code> as a short alias for <code>--flips</code>. The <code>#!/usr/bin/env Rapp</code> line (called a &ldquo;shebang&rdquo;) lets you run the script directly on macOS and Linux without typing <code>Rapp</code> first.</p>
<h3 id="running-the-script">Running the script
</h3>
<p>With Rapp installed and <code>flip-coin</code> available on your <code>PATH</code> (see <a href="#get-started">Get started</a>
 below), you can run the app from the terminal:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">flip-coin -n <span class="m">3</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; heads tails heads</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">flip-coin --seed <span class="m">42</span> -n <span class="m">5</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; tails heads tails tails heads</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="auto-generated-help">Auto-generated help
</h3>
<p>Rapp generates <code>--help</code> from your script (and <code>--help-yaml</code> if you want a machine-readable spec):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">flip-coin --help
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Usage: flip-coin [OPTIONS]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Flip a coin.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Options:
</span></span><span class="line"><span class="cl">  -n, --flips &lt;FLIPS&gt;  Number of coin flips [default: 1] [type: integer]
</span></span><span class="line"><span class="cl">  --sep &lt;SEP&gt;          [default: &#34; &#34;] [type: string]
</span></span><span class="line"><span class="cl">  --wrap / --no-wrap   [default: true] Disable with `--no-wrap`.
</span></span><span class="line"><span class="cl">  --seed &lt;SEED&gt;        [default: NA] [type: integer]
</span></span></code></pre></td></tr></table>
</div>
</div><div class="callout-warning">
<h2 id="breaking-change-in-030-positional-arguments-are-now-required-by-default">Breaking change in 0.3.0: positional arguments are now required by default
</h2>
<p>If you&rsquo;re upgrading from an earlier version of Rapp, note that positional arguments are now <strong>required</strong> unless explicitly marked optional.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Before 0.3.0: this positional was optional</span>
</span></span><span class="line"><span class="cl"><span class="n">name</span> <span class="o">&lt;-</span> <span class="kc">NULL</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># In 0.3.0+: add this comment to keep it optional</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| required: false</span>
</span></span><span class="line"><span class="cl"><span class="n">name</span> <span class="o">&lt;-</span> <span class="kc">NULL</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If your scripts use positional arguments with <code>NULL</code> defaults that should remain optional, add <code>#| required: false</code> above them.</p>
</div>
<h2 id="highlights-in-030">Highlights in 0.3.0
</h2>
<p>Rapp will be new to most readers, so rather than listing every change, here are the main ideas (and what&rsquo;s improved in 0.3.0).</p>
<h3 id="options-switches-and-repeatable-flags-from-plain-r">Options, switches, and repeatable flags from plain R
</h3>
<p>Rapp recognizes a small set of &ldquo;declarative&rdquo; patterns at the top level of your script:</p>
<ul>
<li>Scalar literals like <code>flips &lt;- 1L</code> become options like <code>--flips 10</code>.</li>
<li>Logical defaults like <code>wrap &lt;- TRUE</code> become toggles like <code>--wrap</code> / <code>--no-wrap</code>.</li>
<li><code>#| short: n</code> adds a short alias like <code>-n</code> (new in 0.3.0).</li>
<li><a href="https://rdrr.io/r/base/c.html" target="_blank" rel="noopener"><code>c()</code></a>
 and <a href="https://rdrr.io/r/base/list.html" target="_blank" rel="noopener"><code>list()</code></a>
 defaults declare repeatable options (new in 0.3.0): callers can supply the same flag multiple times and values are appended.</li>
</ul>
<h3 id="subcommands-with-switch">Subcommands with <code>switch()</code>
</h3>
<p>Rapp can now turn a <a href="https://rdrr.io/r/base/switch.html" target="_blank" rel="noopener"><code>switch()</code></a>
 block into subcommands (and you can nest <a href="https://rdrr.io/r/base/switch.html" target="_blank" rel="noopener"><code>switch()</code></a>
 blocks for nested commands). Here&rsquo;s a small sketch of a <code>todo</code>-style app:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env Rapp</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| name: todo</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| description: Manage a simple todo list.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#| description: Path to the todo list file.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| short: s</span>
</span></span><span class="line"><span class="cl"><span class="n">store</span> <span class="o">&lt;-</span> <span class="s">&#34;.todo.yml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">switch</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">command</span> <span class="o">&lt;-</span> <span class="s">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">#| description: Display the todos</span>
</span></span><span class="line"><span class="cl">  <span class="n">list</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">limit</span> <span class="o">&lt;-</span> <span class="m">30L</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">#| description: Add a new todo</span>
</span></span><span class="line"><span class="cl">  <span class="n">add</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">task</span> <span class="o">&lt;-</span> <span class="kc">NULL</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Help is scoped to the command you&rsquo;re asking about, so <code>todo --help</code> lists the commands, and <code>todo list --help</code> shows just the options/arguments for <code>list</code> (plus any parent/global options).</p>
<h3 id="installable-launchers-for-package-clis">Installable launchers for package CLIs
</h3>
<p>A big part of sharing CLI tools is making them easy to run after installation. In <code>0.3.0</code>, <code>install_pkg_cli_apps()</code> installs lightweight launchers for scripts in a package&rsquo;s <code>exec/</code> directory that use either <code>#!/usr/bin/env Rapp</code> or <code>#!/usr/bin/env Rscript</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">Rapp</span><span class="o">::</span><span class="nf">install_pkg_cli_apps</span><span class="p">(</span><span class="s">&#34;mypackage&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>(There&rsquo;s also <code>uninstall_pkg_cli_apps()</code> to remove a package&rsquo;s launchers.)</p>
<h2 id="get-started">Get started
</h2>
<p>Here&rsquo;s the quickest path to your first Rapp script:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># 1. Install the package</span>
</span></span><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;Rapp&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># 2. Install the command-line launcher</span>
</span></span><span class="line"><span class="cl"><span class="n">Rapp</span><span class="o">::</span><span class="nf">install_pkg_cli_apps</span><span class="p">(</span><span class="s">&#34;Rapp&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then create a script (e.g., <code>hello.R</code>):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env Rapp</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| name: hello</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| description: Say hello</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">name</span> <span class="o">&lt;-</span> <span class="s">&#34;world&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nf">cat</span><span class="p">(</span><span class="s">&#34;Hello,&#34;</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And run it:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">Rapp hello.R --name <span class="s2">&#34;R users&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Hello, R users</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="learn-more">Learn more
</h3>
<p>To dig deeper into Rapp:</p>
<ul>
<li>browse examples in the package: <code>system.file(&quot;examples&quot;, package = &quot;Rapp&quot;)</code></li>
<li>read the full documentation: <a href="https://github.com/r-lib/Rapp" target="_blank" rel="noopener">https://github.com/r-lib/Rapp</a>
</li>
<li>note that Rapp requires R ≥ 4.1.0</li>
</ul>
<p>If you try Rapp, we&rsquo;d love feedback! We especially want to hear about your experiences with edge cases in argument parsing, help output, and how commands should feel. Issues and ideas are welcome at <a href="https://github.com/r-lib/Rapp/issues" target="_blank" rel="noopener">https://github.com/r-lib/Rapp/issues</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/rapp-0-3-0/thumbnail-wd.jpg" length="89445" type="image/jpeg" />
    </item>
    <item>
      <title>mirai 2.6.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/mirai-2-6-0/</link>
      <pubDate>Thu, 12 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/mirai-2-6-0/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<p><a href="https://mirai.r-lib.org" target="_blank" rel="noopener">mirai</a>
 2.6.0 is now on CRAN. mirai is R&rsquo;s framework for parallel and asynchronous computing. If you&rsquo;re fitting models, running simulations, or building Shiny apps, mirai lets you spread that work across multiple processes &ndash; locally or on remote infrastructure.</p>
<p>With this release, it bridges the gap between your laptop and enterprise infrastructure &ndash; the same code you prototype locally now deploys to Posit Workbench or any cloud HTTP API, with a single function call.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;mirai&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The flagship feature for this release is the HTTP launcher for deploying daemons to cloud and enterprise platforms. This release also brings a C-level dispatcher for minimal task dispatch overhead, <a href="https://mirai.r-lib.org/reference/race_mirai.html" target="_blank" rel="noopener"><code>race_mirai()</code></a>
 for process-as-completed patterns, synchronous mode for debugging, and daemon synchronization for remote deployments. You can see a full list of changes in the <a href="https://mirai.r-lib.org/news/#mirai-260" target="_blank" rel="noopener">release notes</a>
.</p>
<h2 id="how-mirai-works">How mirai works
</h2>
<p>If you&rsquo;ve ever waited for a loop to finish fitting models, processing files, or calling APIs, mirai can help. Any task that&rsquo;s repeated independently across items is a candidate for parallel execution.</p>
<p>The <a href="https://posit-open-source.netlify.app/blog/tidyverse/2025/mirai-2-5-0">previous release post</a>
 covered mirai&rsquo;s design philosophy in detail. Here&rsquo;s a brief overview for readers encountering mirai for the first time.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://mirai.r-lib.org'>mirai</a></span><span class='o'>)</span></span>
<span><span class='c'># Set up 4 background processes</span></span>
<span><span class='nf'><a href='https://mirai.r-lib.org/reference/daemons.html'>daemons</a></span><span class='o'>(</span><span class='m'>4</span><span class='o'>)</span></span>
<span></span>
<span><span class='c'># Send work -- non-blocking, returns immediately</span></span>
<span><span class='nv'>m</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://mirai.r-lib.org/reference/mirai.html'>mirai</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/Sys.sleep.html'>Sys.sleep</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>)</span></span>
<span>  <span class='m'>100</span> <span class='o'>+</span> <span class='m'>42</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='nv'>m</span></span>
<span><span class='c'>#&gt; &lt; mirai [] &gt;</span></span>
<span></span><span></span>
<span><span class='c'># Collect the result when ready</span></span>
<span><span class='nv'>m</span><span class='o'>[</span><span class='o'>]</span></span>
<span><span class='c'>#&gt; [1] 142</span></span>
<span></span><span></span>
<span><span class='c'># Shut down</span></span>
<span><span class='nf'><a href='https://mirai.r-lib.org/reference/daemons.html'>daemons</a></span><span class='o'>(</span><span class='m'>0</span><span class='o'>)</span></span></code></pre>
</div>
<p>That&rsquo;s mirai in a nutshell: <a href="https://mirai.r-lib.org/reference/daemons.html" target="_blank" rel="noopener"><code>daemons()</code></a>
 to set up workers, <a href="https://mirai.r-lib.org/reference/mirai.html" target="_blank" rel="noopener"><code>mirai()</code></a>
 to send work, <code>[]</code> to collect results. Everything else builds on this.</p>
<p>In mirai&rsquo;s hub architecture, the host session listens at a URL and <em>daemons</em> &ndash; background R processes that do the actual work &ndash; connect to it. You send tasks with <a href="https://mirai.r-lib.org/reference/mirai.html" target="_blank" rel="noopener"><code>mirai()</code></a>
, and the dispatcher routes them to available daemons in first-in, first-out (FIFO) order.</p>
<p>This design enables dynamic scaling: daemons can connect and disconnect at any time without disrupting the host. Add capacity when you need it, release it when you don&rsquo;t.</p>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/mirai-2-6-0/architecture.svg" alt="Hub architecture diagram showing compute profiles with daemons connecting to host" width="100%" />
<p>A single compute profile can mix daemons launched by different methods, and you can run multiple profiles simultaneously to direct different tasks to different resources. The basic syntax for each deployment method:</p>
<table>
  <thead>
      <tr>
          <th>Deploy to</th>
          <th>Setup</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Local</td>
          <td><code>daemons(4)</code></td>
      </tr>
      <tr>
          <td>Remote (SSH)</td>
          <td><code>daemons(url = host_url(), remote = ssh_config(...))</code></td>
      </tr>
      <tr>
          <td>HPC cluster (Slurm, SGE, PBS, LSF)</td>
          <td><code>daemons(url = host_url(), remote = cluster_config())</code></td>
      </tr>
      <tr>
          <td>HTTP API / Posit Workbench</td>
          <td><code>daemons(url = host_url(), remote = http_config())</code></td>
      </tr>
  </tbody>
</table>
<p>Change one line and your local prototype runs on a Slurm cluster. Change it again and it runs on Posit Workbench. Your analysis code stays identical.</p>
<h2 id="the-async-foundation-for-the-modern-r-stack">The async foundation for the modern R stack
</h2>
<p>mirai has become the convergence point for asynchronous and parallel computing across the R ecosystem.</p>
<p>It is the <a href="https://rstudio.github.io/promises/articles/promises_04_mirai.html" target="_blank" rel="noopener">recommended async backend</a>
 for <a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a>
 &ndash; if you&rsquo;re building production Shiny apps, you should be using mirai. It is the <em>only</em> async backend for the next-generation <a href="https://plumber2.posit.co/" target="_blank" rel="noopener">plumber2</a>
 &ndash; if you&rsquo;re building APIs with plumber2, you&rsquo;re already using mirai.</p>
<p>It is the parallel backend for <a href="https://purrr.tidyverse.org/" target="_blank" rel="noopener">purrr</a>
 &ndash; if you use <code>map()</code>, mirai is how you make it parallel. Wrap your function in <a href="https://purrr.tidyverse.org/reference/in_parallel.html" target="_blank" rel="noopener"><code>in_parallel()</code></a>
, set up daemons, and your map calls run across all of them:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">purrr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">models</span> <span class="o">&lt;-</span> <span class="nf">split</span><span class="p">(</span><span class="n">mtcars</span><span class="p">,</span> <span class="n">mtcars</span><span class="o">$</span><span class="n">cyl</span><span class="p">)</span> <span class="o">|&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">map</span><span class="p">(</span><span class="nf">in_parallel</span><span class="p">(</span><span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="nf">lm</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">wt</span> <span class="o">+</span> <span class="n">hp</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">x</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">0</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It powers <a href="https://docs.ropensci.org/targets/" target="_blank" rel="noopener">targets</a>
 &ndash; the pipeline orchestration tool for reproducible analysis. And most recently, <a href="https://ragnar.tidyverse.org/" target="_blank" rel="noopener">ragnar</a>
 &ndash; the Tidyverse package for retrieval-augmented generation (RAG) &ndash; adopted mirai for its parallel processing.</p>
<p>As an <a href="https://stat.ethz.ch/R-manual/R-devel/library/parallel/html/makeCluster.html" target="_blank" rel="noopener">official alternative communications backend</a>
 for R&rsquo;s <code>parallel</code> package, mirai underpins workflows from interactive web applications to pipeline orchestration to AI-powered document processing.</p>
<p>Learn mirai, and you&rsquo;ve learned the async primitive that powers the modern R stack. The same two concepts &ndash; <a href="https://mirai.r-lib.org/reference/daemons.html" target="_blank" rel="noopener"><code>daemons()</code></a>
 to set up workers, <a href="https://mirai.r-lib.org/reference/mirai.html" target="_blank" rel="noopener"><code>mirai()</code></a>
 to send work &ndash; are all you need to keep a Shiny app responsive or run async tasks in production.</p>
<h2 id="http-launcher">HTTP launcher
</h2>
<p>This release extends the &ldquo;deploy everywhere&rdquo; principle with <a href="https://mirai.r-lib.org/reference/http_config.html" target="_blank" rel="noopener"><code>http_config()</code></a>
, a new remote launch configuration that deploys daemons via HTTP API calls &ndash; any platform with an HTTP API for launching jobs.</p>
<h3 id="posit-workbench">Posit Workbench
</h3>
<p>Many organizations use <a href="https://posit.co/products/enterprise/workbench/" target="_blank" rel="noopener">Posit Workbench</a>
 to run research and data science at scale. mirai now integrates directly with it.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Call <a href="https://mirai.r-lib.org/reference/http_config.html" target="_blank" rel="noopener"><code>http_config()</code></a>
 with no arguments and it auto-configures using the Workbench environment:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="n">n</span> <span class="o">=</span> <span class="m">4</span><span class="p">,</span> <span class="n">url</span> <span class="o">=</span> <span class="nf">host_url</span><span class="p">(),</span> <span class="n">remote</span> <span class="o">=</span> <span class="nf">http_config</span><span class="p">())</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>That&rsquo;s it. Four daemons launch as Workbench jobs, connect back to your session, and you can start sending work to them.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/mirai-2-6-0/workbench.png" alt="Posit Workbench session showing launched mirai daemons" />
<figcaption aria-hidden="true">Posit Workbench session showing launched mirai daemons</figcaption>
</figure>
<p>Here&rsquo;s what that looks like in practice: you&rsquo;re developing a model in your Workbench session. Fitting it locally is slow. Add that line, and those fits fan out across four Workbench-managed compute jobs. When you&rsquo;re done, <code>daemons(0)</code> releases them. No YAML, no job scripts, no leaving your R session &ndash; resource allocation, access control, and job lifecycle are all handled by the platform.</p>
<p>If you&rsquo;ve been bitten by expired tokens in long-running sessions, <a href="https://mirai.r-lib.org/reference/http_config.html" target="_blank" rel="noopener"><code>http_config()</code></a>
 is designed to prevent that. Under the hood, it stores <em>functions</em> rather than static values for credentials and endpoint URLs. These functions are called at the moment daemons actually launch, so session cookies and API tokens are always fresh &ndash; even if you created the configuration hours earlier.</p>
<p>See the mirai vignette for <a href="https://mirai.r-lib.org/articles/v01-reference.html#troubleshooting" target="_blank" rel="noopener">troubleshooting</a>
 remote launches.</p>
<h3 id="custom-apis">Custom APIs
</h3>
<p>The HTTP launcher works with any HTTP API, not just Workbench. Supply your own endpoint, authentication, and request body:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">n</span> <span class="o">=</span> <span class="m">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="nf">host_url</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="n">remote</span> <span class="o">=</span> <span class="nf">http_config</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s">&#34;https://api.example.com/launch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">method</span> <span class="o">=</span> <span class="s">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">token</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span> <span class="nf">Sys.getenv</span><span class="p">(</span><span class="s">&#34;MY_API_KEY&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="s">&#39;{&#34;command&#34;: &#34;%s&#34;}&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>&quot;%s&quot;</code> placeholder in <code>data</code> is where mirai inserts the daemon launch command at launch time. Each argument can be a plain value or a function &ndash; use functions for anything that changes between launches (tokens, cookies, dynamic URLs).</p>
<p>This opens up a wide range of deployment targets: Kubernetes job APIs, other cloud container services, or any internal job scheduler with an HTTP interface. If you can launch a process with an HTTP call, mirai can use it.</p>
<h2 id="c-level-dispatcher">C-level dispatcher
</h2>
<p>The overhead of distributing your tasks is now negligible. In a <a href="https://mirai.r-lib.org/reference/mirai_map.html" target="_blank" rel="noopener"><code>mirai_map()</code></a>
 over thousands of items, what you measure is the time of your actual computation, not the framework &ndash; per-task dispatch overhead is now in the tens of microseconds, where existing R parallelism solutions typically operate in the millisecond range.</p>
<p>Under the hood, the dispatcher &ndash; the process that sits between your session and the daemons, routing tasks to available workers &ndash; has been re-implemented entirely in C code within <a href="https://nanonext.r-lib.org" target="_blank" rel="noopener">nanonext</a>
. This eliminates the R interpreter overhead that remained, while the dispatcher continues to be event-driven and consume zero CPU when idle.</p>
<p>This also removes the bottleneck when coordinating large numbers of daemons, which matters directly for the kind of scaled-out deployments that the HTTP launcher enables &ndash; dozens of Workbench jobs or cloud instances all connecting to a single dispatcher. The two features are designed to work together: deploy broadly, dispatch efficiently. mirai is built to scale from 2 cores on your laptop to 200 across a cluster, without the framework slowing you down.</p>
<h2 id="race_mirai"><code>race_mirai()</code>
</h2>
<p><a href="https://mirai.r-lib.org/reference/race_mirai.html" target="_blank" rel="noopener"><code>race_mirai()</code></a>
 lets you process results as they arrive, rather than waiting for the slowest task. Suppose you&rsquo;re fitting 10 models with different hyperparameters in parallel &ndash; some converge quickly, others take much longer. Without <a href="https://mirai.r-lib.org/reference/race_mirai.html" target="_blank" rel="noopener"><code>race_mirai()</code></a>
, you wait for the slowest fit to complete before seeing any results. With it, you can inspect or save each model the instant it finishes &ndash; updating a progress display, freeing memory, or deciding whether to continue the remaining fits at all.</p>
<p><a href="https://mirai.r-lib.org/reference/race_mirai.html" target="_blank" rel="noopener"><code>race_mirai()</code></a>
 returns the integer <em>index</em> of the first resolved mirai. This makes the &ldquo;process as completed&rdquo; pattern clean and efficient:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Launch 10 model fits in parallel</span>
</span></span><span class="line"><span class="cl"><span class="n">fits</span> <span class="o">&lt;-</span> <span class="nf">lapply</span><span class="p">(</span><span class="n">param_grid</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="nf">mirai</span><span class="p">(</span><span class="nf">fit_model</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">p</span><span class="p">),</span> <span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="p">,</span> <span class="n">p</span> <span class="o">=</span> <span class="n">p</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Process each result as soon as it&#39;s ready</span>
</span></span><span class="line"><span class="cl"><span class="n">remaining</span> <span class="o">&lt;-</span> <span class="n">fits</span>
</span></span><span class="line"><span class="cl"><span class="kr">while</span> <span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="n">remaining</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">idx</span> <span class="o">&lt;-</span> <span class="nf">race_mirai</span><span class="p">(</span><span class="n">remaining</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;Finished model with params:&#34;</span><span class="p">,</span> <span class="n">remaining[[idx]]</span><span class="o">$</span><span class="n">data</span><span class="o">$</span><span class="n">p</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">remaining</span> <span class="o">&lt;-</span> <span class="n">remaining[</span><span class="o">-</span><span class="n">idx]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">0</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Send off a batch of tasks, then process results in the order they finish &ndash; no polling, no wasted time waiting on the slowest one. If any mirai is already resolved when you call <a href="https://mirai.r-lib.org/reference/race_mirai.html" target="_blank" rel="noopener"><code>race_mirai()</code></a>
, it returns immediately. This pattern applies whenever tasks have variable completion times &ndash; parallel model fits, API calls, simulations, or any batch where you want to stream results as they land.</p>
<h2 id="synchronous-mode">Synchronous mode
</h2>
<p>When tasks don&rsquo;t behave as expected, you need a way to inspect them interactively.</p>
<p>Without synchronous mode, errors in a mirai return as <code>miraiError</code> objects &ndash; you can see that something went wrong, but you can&rsquo;t step through the code to find out why. The task ran in a separate process, and by the time you see the error, that process has moved on.</p>
<p><code>daemons(sync = TRUE)</code>, introduced in 2.5.1, solves this. It runs everything in the current process &ndash; no background processes, no networking &ndash; just sequential execution. You can use <a href="https://rdrr.io/r/base/browser.html" target="_blank" rel="noopener"><code>browser()</code></a>
 and other interactive debugging tools directly:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="n">sync</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">mirai</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">browser</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">mypkg</span><span class="o">::</span><span class="nf">some_complex_function</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">x</span> <span class="o">=</span> <span class="n">my_data</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You can scope synchronous mode to a specific compute profile, isolating the problematic task for inspection while the rest of your pipeline keeps running in parallel.</p>
<h2 id="daemon-synchronization-with-everywhere">Daemon synchronization with <code>everywhere()</code>
</h2>
<p><a href="https://mirai.r-lib.org/reference/everywhere.html" target="_blank" rel="noopener"><code>everywhere()</code></a>
 runs setup operations on all daemons &ndash; loading packages, sourcing scripts, or preparing datasets &ndash; so they&rsquo;re ready before you send work.</p>
<p>When launching remote daemons &ndash; via SSH, HPC schedulers, or the new HTTP launcher &ndash; there&rsquo;s an inherent delay between requesting a daemon and that daemon being ready to accept work. The new <code>.min</code> argument ensures that setup has completed on at least that many daemons before returning:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="n">n</span> <span class="o">=</span> <span class="m">8</span><span class="p">,</span> <span class="n">url</span> <span class="o">=</span> <span class="nf">host_url</span><span class="p">(),</span> <span class="n">remote</span> <span class="o">=</span> <span class="nf">http_config</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wait until all 8 daemons are connected before continuing</span>
</span></span><span class="line"><span class="cl"><span class="nf">everywhere</span><span class="p">(</span><span class="nf">library</span><span class="p">(</span><span class="n">mypackage</span><span class="p">),</span> <span class="n">.min</span> <span class="o">=</span> <span class="m">8</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Now send work once all daemons are ready</span>
</span></span><span class="line"><span class="cl"><span class="n">mp</span> <span class="o">&lt;-</span> <span class="nf">mirai_map</span><span class="p">(</span><span class="n">tasks</span><span class="p">,</span> <span class="n">process</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This creates a synchronization point, ensuring your pipeline doesn&rsquo;t start sending work before all daemons are ready. It&rsquo;s especially useful for remote deployments where connection times are unpredictable.</p>
<h2 id="minor-improvements-and-fixes">Minor improvements and fixes
</h2>
<ul>
<li><code>miraiError</code> objects now have <a href="https://rdrr.io/r/base/conditions.html" target="_blank" rel="noopener"><code>conditionCall()</code></a>
 and <a href="https://rdrr.io/r/base/conditions.html" target="_blank" rel="noopener"><code>conditionMessage()</code></a>
 methods, making them easier to use with R&rsquo;s standard condition handling.</li>
<li>The default exit behavior for daemons has been updated with a 200ms grace period before forceful termination, which allows OpenTelemetry disconnection events to be traced.</li>
<li>OpenTelemetry span names and attributes have been revised to better follow semantic conventions.</li>
<li><a href="https://mirai.r-lib.org/reference/daemons.html" target="_blank" rel="noopener"><code>daemons()</code></a>
 now properly validates that <code>url</code> is a character value where supplied.</li>
<li>Fixed a bug where repeated mirai cancellation could sometimes cause a daemon to exit prematurely.</li>
</ul>
<h2 id="try-it-now">Try it now
</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;mirai&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mirai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">system.time</span><span class="p">(</span><span class="nf">mirai_map</span><span class="p">(</span><span class="m">1</span><span class="o">:</span><span class="m">4</span><span class="p">,</span> <span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="nf">Sys.sleep</span><span class="p">(</span><span class="m">1</span><span class="p">))</span><span class="n">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    user  system elapsed</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   0.000   0.001   1.003</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">0</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Four one-second tasks, one second of wall time. If those were four model fits that each took a minute, you&rsquo;d go from four minutes down to one &ndash; and if you needed more power, switching to Workbench or a Slurm cluster is a one-line change. Visit <a href="https://mirai.r-lib.org" target="_blank" rel="noopener">mirai.r-lib.org</a>
 for the full documentation.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to all the folks who helped make this release happen:</p>
<p><a href="https://github.com/agilly" target="_blank" rel="noopener">@agilly</a>
, <a href="https://github.com/aimundo" target="_blank" rel="noopener">@aimundo</a>
, <a href="https://github.com/barnabasharris" target="_blank" rel="noopener">@barnabasharris</a>
, <a href="https://github.com/beevabeeva" target="_blank" rel="noopener">@beevabeeva</a>
, <a href="https://github.com/boshek" target="_blank" rel="noopener">@boshek</a>
, <a href="https://github.com/eliocamp" target="_blank" rel="noopener">@eliocamp</a>
, <a href="https://github.com/jan-swissre" target="_blank" rel="noopener">@jan-swissre</a>
, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>
, <a href="https://github.com/kentqin-cve" target="_blank" rel="noopener">@kentqin-cve</a>
, <a href="https://github.com/mcol" target="_blank" rel="noopener">@mcol</a>
, <a href="https://github.com/michaelmayer2" target="_blank" rel="noopener">@michaelmayer2</a>
, <a href="https://github.com/pmac0451" target="_blank" rel="noopener">@pmac0451</a>
, <a href="https://github.com/r2evans" target="_blank" rel="noopener">@r2evans</a>
, <a href="https://github.com/shikokuchuo" target="_blank" rel="noopener">@shikokuchuo</a>
, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>
, <a href="https://github.com/VincentGuyader" target="_blank" rel="noopener">@VincentGuyader</a>
, <a href="https://github.com/wlandau" target="_blank" rel="noopener">@wlandau</a>
, and <a href="https://github.com/xwanner" target="_blank" rel="noopener">@xwanner</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Requires Posit Workbench version 2026.01 or later, which enables launcher authentication using the session cookie.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/mirai-2-6-0/thumbnail-wd.jpg" length="208853" type="image/jpeg" />
    </item>
    <item>
      <title>nanonext 1.8.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/nanonext-1-8-0/</link>
      <pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/nanonext-1-8-0/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>When we <a href="https://posit-open-source.netlify.app/blog/tidyverse/2025/nanonext-1-7-0/">introduced nanonext</a>
 last year, we showed how it connects R directly to Python, Go, Rust, and other languages through NNG&rsquo;s messaging protocols. We hinted at its web capabilities &ndash; but that was just the beginning.</p>
<p>R already has excellent web infrastructure. <a href="https://shiny.posit.co/" target="_blank" rel="noopener">Shiny</a>
 and <a href="https://plumber2.posit.co/" target="_blank" rel="noopener">plumber2</a>
 are the go-to tools for building interactive applications and REST APIs in R. They are both powered by <a href="https://rstudio.github.io/httpuv/" target="_blank" rel="noopener">httpuv</a>
. <a href="https://nanonext.r-lib.org" target="_blank" rel="noopener">nanonext</a>
 adds a complementary option at the httpuv level of the stack &ndash; a low-level streaming HTTP and WebSocket server built on NNG, giving developers fine-grained control over connections, streaming, and static file serving over TLS. nanonext is for when you need lower-level control &ndash; custom protocols, infrastructure endpoints, or embedding a server alongside an existing Shiny or plumber2 application.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;nanonext&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You can see a full list of changes in the <a href="https://nanonext.r-lib.org/news/#nanonext-180" target="_blank" rel="noopener">release notes</a>
.</p>
<h2 id="streaming-httpwebsocket-server">Streaming HTTP/WebSocket server
</h2>
<p>The flagship feature of this release is <a href="https://nanonext.r-lib.org/reference/http_server.html" target="_blank" rel="noopener"><code>http_server()</code></a>
, a streaming HTTP and WebSocket server with full TLS support. Built on NNG&rsquo;s HTTP server architecture, it brings the same performance that powers nanonext&rsquo;s messaging layer to web serving.</p>
<p>One server, one port &ndash; HTTP endpoints, WebSocket connections, and streaming all coexist. Static files bypass R entirely, served natively by NNG. WebSocket and streaming connections run callbacks on R&rsquo;s main thread via the later package. Mbed TLS is built in for HTTPS/WSS, and there&rsquo;s no need to run separate processes or bind additional ports.</p>
<p>Because it shares the same event loop that Shiny uses, <a href="https://nanonext.r-lib.org/reference/http_server.html" target="_blank" rel="noopener"><code>http_server()</code></a>
 can run alongside a Shiny app in the same R process. You could spin up a nanonext server to handle health checks, serve static assets, or stream real-time events &ndash; while Shiny or plumber2 handles the application logic. They&rsquo;re designed to work together.</p>
<p>As part of our investment in expanding what&rsquo;s possible with R, we&rsquo;re already using nanonext at Posit to explore new real-time capabilities, and we&rsquo;re excited to see what the community builds with it.</p>
<h3 id="basic-http-server">Basic HTTP server
</h3>
<p>Where frameworks like plumber2 give you a full-featured API layer with routing, serialization, and documentation out of the box, <a href="https://nanonext.r-lib.org/reference/http_server.html" target="_blank" rel="noopener"><code>http_server()</code></a>
 gives you direct control over requests and responses &ndash; the kind of direct access you&rsquo;d reach for when building custom infrastructure or embedding a server inside a larger system.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">nanonext</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="nf">http_server</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s">&#34;http://127.0.0.1:8080&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">handlers</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">handler</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">list</span><span class="p">(</span><span class="n">status</span> <span class="o">=</span> <span class="m">200L</span><span class="p">,</span> <span class="n">body</span> <span class="o">=</span> <span class="s">&#34;Hello from nanonext!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">handler</span><span class="p">(</span><span class="s">&#34;/api/data&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">status</span> <span class="o">=</span> <span class="m">200L</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">headers</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span> <span class="o">=</span> <span class="s">&#34;application/json&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="s">&#39;{&#34;value&#34;: 42}&#39;</span>
</span></span><span class="line"><span class="cl">      <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span> <span class="n">method</span> <span class="o">=</span> <span class="s">&#34;GET&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span><span class="o">$</span><span class="nf">start</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Handlers receive a request and return a response list. You can freely mix handler types in a single server:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Handler</th>
          <th style="text-align: left">Purpose</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler.html" target="_blank" rel="noopener"><code>handler()</code></a>
</td>
          <td style="text-align: left">HTTP request/response with R callback</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler_ws.html" target="_blank" rel="noopener"><code>handler_ws()</code></a>
</td>
          <td style="text-align: left">WebSocket with <code>on_message</code>, <code>on_open</code>, <code>on_close</code> callbacks</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler_stream.html" target="_blank" rel="noopener"><code>handler_stream()</code></a>
</td>
          <td style="text-align: left">Chunked HTTP streaming (SSE, NDJSON, custom)</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler_file.html" target="_blank" rel="noopener"><code>handler_file()</code></a>
</td>
          <td style="text-align: left">Serve a single static file</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler_directory.html" target="_blank" rel="noopener"><code>handler_directory()</code></a>
</td>
          <td style="text-align: left">Serve a directory tree with automatic MIME types</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler_inline.html" target="_blank" rel="noopener"><code>handler_inline()</code></a>
</td>
          <td style="text-align: left">Serve in-memory content</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/reference/handler_redirect.html" target="_blank" rel="noopener"><code>handler_redirect()</code></a>
</td>
          <td style="text-align: left">HTTP redirect</td>
      </tr>
  </tbody>
</table>
<p>Specifying port <code>0</code> in the URL lets the operating system assign an available port. The actual port is reflected in <code>server$url</code> after <code>$start()</code>, so you can set up test servers without worrying about port conflicts.</p>
<h3 id="static-file-serving">Static file serving
</h3>
<p>Static handlers bypass R entirely &ndash; NNG serves content directly and efficiently:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">handler_directory</span><span class="p">(</span><span class="s">&#34;/static&#34;</span><span class="p">,</span> <span class="s">&#34;www/assets&#34;</span><span class="p">)</span>  <span class="c1"># serve a folder</span>
</span></span><span class="line"><span class="cl"><span class="nf">handler_file</span><span class="p">(</span><span class="s">&#34;/favicon.ico&#34;</span><span class="p">,</span> <span class="s">&#34;favicon.ico&#34;</span><span class="p">)</span> <span class="c1"># serve a single file</span>
</span></span><span class="line"><span class="cl"><span class="nf">handler_inline</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;/robots.txt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;User-agent: *\nDisallow:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">content_type</span> <span class="o">=</span> <span class="s">&#34;text/plain&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="c1"># serve in-memory content</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For example, you can serve a rendered Quarto website with a single handler:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="nf">http_server</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s">&#34;http://127.0.0.1:0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">handlers</span> <span class="o">=</span> <span class="nf">handler_directory</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="s">&#34;_site&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span><span class="o">$</span><span class="nf">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span><span class="o">$</span><span class="n">url</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Browse to the URL to see your Quarto site</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="websocket-server">WebSocket server
</h3>
<p>WebSockets provide full bidirectional communication &ndash; the server can push messages to the client, and the client can send messages back. WebSocket and HTTP handlers share the same server and port, so a browser can load a page over HTTP and open a WebSocket to the same origin &ndash; no cross-origin configuration needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="nf">http_server</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s">&#34;http://127.0.0.1:8080&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">handlers</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nf">handler</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="nf">list</span><span class="p">(</span><span class="n">status</span> <span class="o">=</span> <span class="m">200L</span><span class="p">,</span> <span class="n">body</span> <span class="o">=</span> <span class="s">&#34;&lt;html&gt;...&lt;/html&gt;&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">    <span class="nf">handler_ws</span><span class="p">(</span><span class="s">&#34;/ws&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="n">on_message</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">ws</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span> <span class="n">ws</span><span class="o">$</span><span class="nf">send</span><span class="p">(</span><span class="n">data</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">on_open</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">ws</span><span class="p">)</span> <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;connected:&#34;</span><span class="p">,</span> <span class="n">ws</span><span class="o">$</span><span class="n">id</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="n">on_close</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">ws</span><span class="p">)</span> <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;disconnected:&#34;</span><span class="p">,</span> <span class="n">ws</span><span class="o">$</span><span class="n">id</span><span class="p">,</span> <span class="s">&#34;\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This makes it easy to build lightweight real-time services &ndash; monitoring endpoints or live-updating feeds that push results to the browser as they arrive.</p>
<h3 id="http-streaming-and-server-sent-events">HTTP streaming and Server-Sent Events
</h3>
<p>When you only need to push data in one direction &ndash; server to client &ndash; streaming is a lighter-weight alternative to WebSockets. It works over plain HTTP, so any client that speaks HTTP can consume the stream without needing a WebSocket library. <a href="https://nanonext.r-lib.org/reference/handler_stream.html" target="_blank" rel="noopener"><code>handler_stream()</code></a>
 enables chunked transfer encoding for streaming responses:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">conns</span> <span class="o">&lt;-</span> <span class="nf">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">handler_stream</span><span class="p">(</span><span class="s">&#34;/events&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">on_request</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">req</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">conn</span><span class="o">$</span><span class="nf">set_header</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">,</span> <span class="s">&#34;text/event-stream&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">conn</span><span class="o">$</span><span class="nf">set_header</span><span class="p">(</span><span class="s">&#34;Cache-Control&#34;</span><span class="p">,</span> <span class="s">&#34;no-cache&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">conns[</span><span class="nf">[as.character</span><span class="p">(</span><span class="n">conn</span><span class="o">$</span><span class="n">id</span><span class="p">)</span><span class="n">]]</span> <span class="o">&lt;&lt;-</span> <span class="n">conn</span>
</span></span><span class="line"><span class="cl">    <span class="n">conn</span><span class="o">$</span><span class="nf">send</span><span class="p">(</span><span class="nf">format_sse</span><span class="p">(</span><span class="n">data</span> <span class="o">=</span> <span class="s">&#34;connected&#34;</span><span class="p">,</span> <span class="n">id</span> <span class="o">=</span> <span class="s">&#34;1&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">on_close</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">conns[</span><span class="nf">[as.character</span><span class="p">(</span><span class="n">conn</span><span class="o">$</span><span class="n">id</span><span class="p">)</span><span class="n">]]</span> <span class="o">&lt;&lt;-</span> <span class="kc">NULL</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <a href="https://nanonext.r-lib.org/reference/format_sse.html" target="_blank" rel="noopener"><code>format_sse()</code></a>
 helper formats messages per the SSE specification. On the browser side, updates arrive automatically as they happen &ndash; no page refreshes or repeated requests needed. Streaming also supports NDJSON and custom formats &ndash; useful for streaming model training progress, sensor readings, monitoring endpoints, or pipeline notifications.</p>
<h3 id="tlsssl-support">TLS/SSL support
</h3>
<p>For HTTPS, pass a TLS configuration. nanonext bundles Mbed TLS, so there&rsquo;s nothing extra to install:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">cert</span> <span class="o">&lt;-</span> <span class="nf">write_cert</span><span class="p">(</span><span class="n">cn</span> <span class="o">=</span> <span class="s">&#34;127.0.0.1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="nf">http_server</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s">&#34;https://127.0.0.1:0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">handlers</span> <span class="o">=</span> <span class="nf">handler</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="nf">list</span><span class="p">(</span><span class="n">status</span> <span class="o">=</span> <span class="m">200L</span><span class="p">,</span> <span class="n">body</span> <span class="o">=</span> <span class="s">&#34;Secure!&#34;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">  <span class="n">tls</span> <span class="o">=</span> <span class="nf">tls_config</span><span class="p">(</span><span class="n">server</span> <span class="o">=</span> <span class="n">cert</span><span class="o">$</span><span class="n">server</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="full-response-headers-for-http-client">Full response headers for HTTP client
</h2>
<p><a href="https://nanonext.r-lib.org/reference/ncurl.html" target="_blank" rel="noopener"><code>ncurl()</code></a>
 now accepts <code>response = TRUE</code> to return all response headers:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>resp</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://nanonext.r-lib.org/reference/ncurl.html'>ncurl</a></span><span class='o'>(</span><span class='s'>"https://postman-echo.com/get"</span>, response <span class='o'>=</span> <span class='kc'>TRUE</span><span class='o'>)</span></span>
<span><span class='nv'>resp</span><span class='o'>$</span><span class='nv'>headers</span> <span class='o'>|&gt;</span> <span class='nf'><a href='https://rdrr.io/r/base/names.html'>names</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt;  [1] "Date"                          "Content-Type"                 </span></span>
<span><span class='c'>#&gt;  [3] "Content-Length"                "Connection"                   </span></span>
<span><span class='c'>#&gt;  [5] "CF-RAY"                        "etag"                         </span></span>
<span><span class='c'>#&gt;  [7] "vary"                          "Set-Cookie"                   </span></span>
<span><span class='c'>#&gt;  [9] "x-envoy-upstream-service-time" "cf-cache-status"              </span></span>
<span><span class='c'>#&gt; [11] "Server"</span></span>
<span></span></code></pre>
</div>
<p>Previously you could only request specific headers by name. Now you can retrieve the complete set &ndash; useful for inspecting rate limits, caching directives, and other metadata from REST APIs.</p>
<h2 id="async-http-with-shiny">Async HTTP with Shiny
</h2>
<p>If your Shiny app calls a REST API, a slow or unresponsive endpoint will block the R process and freeze the app for <em>all</em> users, not just the one who triggered the request. <a href="https://nanonext.r-lib.org/reference/ncurl_aio.html" target="_blank" rel="noopener"><code>ncurl_aio()</code></a>
 avoids this &ndash; it performs the HTTP call on a background thread and returns a promise, so the R process stays free to serve other sessions. It works anywhere that accepts a promise, including Shiny&rsquo;s ExtendedTask:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">bslib</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">nanonext</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="nf">page_fluid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">p</span><span class="p">(</span><span class="s">&#34;The time is &#34;</span><span class="p">,</span> <span class="nf">textOutput</span><span class="p">(</span><span class="s">&#34;current_time&#34;</span><span class="p">,</span> <span class="n">inline</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">hr</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">input_task_button</span><span class="p">(</span><span class="s">&#34;btn&#34;</span><span class="p">,</span> <span class="s">&#34;Fetch data&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="nf">verbatimTextOutput</span><span class="p">(</span><span class="s">&#34;result&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">server</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">current_time</span> <span class="o">&lt;-</span> <span class="nf">renderText</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nf">invalidateLater</span><span class="p">(</span><span class="m">1000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">format</span><span class="p">(</span><span class="nf">Sys.time</span><span class="p">(),</span> <span class="s">&#34;%H:%M:%S %p&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">task</span> <span class="o">&lt;-</span> <span class="n">ExtendedTask</span><span class="o">$</span><span class="nf">new</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="kr">function</span><span class="p">()</span> <span class="nf">ncurl_aio</span><span class="p">(</span><span class="s">&#34;https://postman-echo.com/get&#34;</span><span class="p">,</span> <span class="n">response</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">bind_task_button</span><span class="p">(</span><span class="s">&#34;btn&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nf">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">btn</span><span class="p">,</span> <span class="n">task</span><span class="o">$</span><span class="nf">invoke</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">$</span><span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">renderPrint</span><span class="p">(</span><span class="n">task</span><span class="o">$</span><span class="nf">result</span><span class="p">()</span><span class="o">$</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shinyApp</span><span class="p">(</span><span class="n">ui</span><span class="p">,</span> <span class="n">server</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="new-documentation">New documentation
</h2>
<p>The package documentation has been reorganized into focused, self-contained guides:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Guide</th>
          <th style="text-align: left">Topics</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/articles/nanonext.html" target="_blank" rel="noopener">Quick Reference</a>
</td>
          <td style="text-align: left">At-a-glance API overview</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/articles/v01-messaging.html" target="_blank" rel="noopener">Messaging</a>
</td>
          <td style="text-align: left">Cross-language exchange, async I/O, synchronization</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/articles/v02-protocols.html" target="_blank" rel="noopener">Protocols</a>
</td>
          <td style="text-align: left">req/rep, pub/sub, surveyor/respondent</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/articles/v03-configuration.html" target="_blank" rel="noopener">Configuration</a>
</td>
          <td style="text-align: left">TLS, options, serialization</td>
      </tr>
      <tr>
          <td style="text-align: left"><a href="https://nanonext.r-lib.org/articles/v04-web.html" target="_blank" rel="noopener">Web Toolkit</a>
</td>
          <td style="text-align: left">HTTP client/server, WebSocket, streaming</td>
      </tr>
  </tbody>
</table>
<p>Whether you need a quick API cheatsheet or a deep dive into WebSocket chat servers, the new vignettes are designed to get you up and running fast.</p>
<h2 id="bug-fixes-and-improvements">Bug fixes and improvements
</h2>
<p>A new <a href="https://nanonext.r-lib.org/reference/race_aio.html" target="_blank" rel="noopener"><code>race_aio()</code></a>
 function returns the index of the first resolved async operation in a list &ndash; useful when waiting on multiple concurrent operations and you want to act on whichever completes first.</p>
<p>This release also fixes two critical issues &ndash; one affecting TLS operations in fresh sessions with newer system versions of Mbed TLS, another when custom serialization hooks threw errors. Error handling is now more graceful throughout, with closed streams returning error values instead of throwing. Under the hood, serialization, streaming, and async sends are all faster, and the bundled Mbed TLS is updated to 3.6.5 LTS. Building from source no longer requires <code>xz</code>.</p>
<h2 id="looking-ahead">Looking ahead
</h2>
<p>nanonext gives R a new building block for web infrastructure &ndash; one that complements httpuv. We see it as part of a broader investment in making R a first-class platform for real-time, connected applications. If you want to dig deeper, visit the <a href="https://nanonext.r-lib.org" target="_blank" rel="noopener">package website</a>
 or explore the source on <a href="https://github.com/r-lib/nanonext" target="_blank" rel="noopener">GitHub</a>
.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to everyone who contributed to this release:</p>
<p><a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>
 and <a href="https://github.com/shikokuchuo" target="_blank" rel="noopener">@shikokuchuo</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/nanonext-1-8-0/thumbnail-wd.jpg" length="278046" type="image/jpeg" />
    </item>
    <item>
      <title>yaml12: YAML 1.2 for R and Python</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/yaml12-0-1-0/</link>
      <pubDate>Wed, 07 Jan 2026 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/yaml12-0-1-0/</guid>
      <dc:creator>Tomasz Kalinowski</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [ ] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html) (optional)
-->
<p>Today we&rsquo;re announcing two new packages for parsing and emitting YAML 1.2: <a href="https://posit-dev.github.io/r-yaml12/" target="_blank" rel="noopener"><code>yaml12</code></a>
 for R and <a href="https://posit-dev.github.io/py-yaml12/" target="_blank" rel="noopener"><code>py-yaml12</code></a>
 for Python.</p>
<p>Both packages are implemented in Rust and built on the excellent <a href="https://github.com/saphyr-rs/saphyr" target="_blank" rel="noopener"><code>saphyr</code></a>
 crate. They share the same design goals: predictable YAML 1.2 typing, explicit control over tag interpretation via handlers, and clean round-tripping of unhandled tags.</p>
<p>Before we get into the details, a quick note on how this relates to the existing R <a href="https://github.com/r-lib/yaml" target="_blank" rel="noopener"><code>yaml</code></a>
 package. The R <code>yaml</code> package is now in <a href="https://github.com/r-lib" target="_blank" rel="noopener">r-lib</a>
, and we&rsquo;ve taken over maintenance after years of stewardship by its original author, Jeremy Stephens, and later by Shawn Garbett.</p>
<p>If <code>yaml</code> already works for you, there&rsquo;s no need to switch. <code>yaml12</code> is an experiment providing consistent R and Python bindings to a new Rust library specifically for YAML 1.2, which, as we&rsquo;ll see below, has some particular advantages.</p>
<h2 id="install">Install
</h2>
<p>Install the R package from CRAN:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"yaml12"</span><span class='o'>)</span></span></code></pre>
</div>
<p>Install the Python package from PyPI:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install py-yaml12
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="quick-start-r">Quick start (R)
</h2>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://posit-dev.github.io/r-yaml12/'>yaml12</a></span><span class='o'>)</span></span>
<span></span>
<span><span class='nv'>yaml</span> <span class='o'>&lt;-</span> <span class='s'>"</span></span>
<span><span class='s'>title: A modern YAML parser and emitter written in Rust</span></span>
<span><span class='s'>properties: [fast, correct, safe, simple]</span></span>
<span><span class='s'>"</span></span>
<span></span>
<span><span class='nv'>doc</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='nv'>yaml</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/utils/str.html'>str</a></span><span class='o'>(</span><span class='nv'>doc</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; List of 2</span></span>
<span><span class='c'>#&gt;  $ title     : chr "A modern YAML parser and emitter written in Rust"</span></span>
<span><span class='c'>#&gt;  $ properties: chr [1:4] "fast" "correct" "safe" "simple"</span></span>
<span></span></code></pre>
</div>
<p>Round-trip back to YAML:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>obj</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span></span>
<span>  seq <span class='o'>=</span> <span class='m'>1</span><span class='o'>:</span><span class='m'>2</span>,</span>
<span>  map <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span>key <span class='o'>=</span> <span class='s'>"value"</span><span class='o'>)</span>,</span>
<span>  tagged <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/structure.html'>structure</a></span><span class='o'>(</span><span class='s'>"1 + 1"</span>, yaml_tag <span class='o'>=</span> <span class='s'>"!expr"</span><span class='o'>)</span></span>
<span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/format_yaml.html'>write_yaml</a></span><span class='o'>(</span><span class='nv'>obj</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ---</span></span>
<span><span class='c'>#&gt; seq:</span></span>
<span><span class='c'>#&gt;   - 1</span></span>
<span><span class='c'>#&gt;   - 2</span></span>
<span><span class='c'>#&gt; map:</span></span>
<span><span class='c'>#&gt;   key: value</span></span>
<span><span class='c'>#&gt; tagged: !expr 1 + 1</span></span>
<span><span class='c'>#&gt; ...</span></span>
<span></span><span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/identical.html'>identical</a></span><span class='o'>(</span><span class='nv'>obj</span>, <span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/format_yaml.html'>format_yaml</a></span><span class='o'>(</span><span class='nv'>obj</span><span class='o'>)</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] TRUE</span></span>
<span></span></code></pre>
</div>
<h2 id="quick-start-python">Quick start (Python)
</h2>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'># Install from PyPI:
#   python -m pip install py-yaml12
from yaml12 import parse_yaml, format_yaml, Yaml

yaml_text = """
title: A modern YAML parser and emitter written in Rust
properties: [fast, correct, safe, simple]
"""

doc = parse_yaml(yaml_text)

assert doc == {
  "title": "A modern YAML parser and emitter written in Rust",
  "properties": ["fast", "correct", "safe", "simple"]
}

assert doc == parse_yaml(format_yaml(doc))

# Tagged values
tagged = parse_yaml("!expr 1 + 1")
assert tagged == Yaml(value="1 + 1", tag="!expr")
</code></pre>
</div>
<h2 id="why-yaml-12">Why YAML 1.2?
</h2>
<p>YAML 1.2 tightened up a number of ambiguous implicit conversions. In particular, plain scalars like <code>on</code>/<code>off</code>/<code>yes</code>/<code>no</code>/<code>y</code>/<code>n</code> are strings in the 1.2 core schema, and YAML 1.2 removed sexagesimal (base-60) parsing, so values like <code>1:2</code> are not treated as numbers.</p>
<p>YAML 1.2 also removed <code>!!timestamp</code>, <code>!!binary</code>, and <code>!!omap</code> from the set of core types, which further reduces implicit coercions (for example, getting a date/time object when you expected a string). If you want to interpret those values, you can do so explicitly via tags and handlers.</p>
<p>That makes YAML a better default for configuration files, front matter, and data interchange. You get fewer surprises and fewer &ldquo;why did this become a boolean?&rdquo; moments (or &ldquo;why did this become a date?&rdquo;).</p>
<h2 id="highlights">Highlights
</h2>
<h3 id="a-consistent-api-in-r-and-python">A consistent API in R and Python
</h3>
<p>The two packages intentionally share the same high-level functions:</p>
<ul>
<li><a href="https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html" target="_blank" rel="noopener"><code>parse_yaml()</code></a>
: Parse YAML from a string</li>
<li><a href="https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html" target="_blank" rel="noopener"><code>read_yaml()</code></a>
: Read YAML from a file</li>
<li><a href="https://posit-dev.github.io/r-yaml12/reference/format_yaml.html" target="_blank" rel="noopener"><code>format_yaml()</code></a>
: Format values as YAML (to a string)</li>
<li><a href="https://posit-dev.github.io/r-yaml12/reference/format_yaml.html" target="_blank" rel="noopener"><code>write_yaml()</code></a>
: Write YAML to a file (or stdout)</li>
</ul>
<h3 id="tags-and-handlers-opt-in-meaning-safe-defaults">Tags and handlers (opt-in, meaning, safe defaults)
</h3>
<p>In YAML, tags are explicit annotations like <code>!expr</code> or <code>!!timestamp</code> that attach type and meaning to a value.</p>
<p>Tags are preserved by default:</p>
<ul>
<li>In R, tags are kept in a <code>yaml_tag</code> attribute.</li>
<li>In Python, tags are kept by wrapping values in a <code>Yaml()</code> object.</li>
</ul>
<p>Handlers let you opt into custom behavior for tags (including tags on mapping keys) while keeping parsing as a data-only operation by default.</p>
<p>If you used R <code>yaml</code>&rsquo;s <code>!expr</code> tag to evaluate expressions, you can recreate that behavior by registering a handler, but it&rsquo;s only recommended when parsing trusted YAML, since evaluating arbitrary code is a security risk. For untrusted input, the default behavior is safer because it keeps <code>!expr</code> as data and does not execute code.</p>
<p>R example:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># by default, tags are kept as data</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/dput.html'>dput</a></span><span class='o'>(</span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='s'>"!expr 1 + 1"</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; structure("1 + 1", yaml_tag = "!expr")</span></span>
<span></span><span></span>
<span><span class='c'># Add a handler to process tagged nodes (like the &#123;yaml&#125; package does)</span></span>
<span><span class='nv'>handlers</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span><span class='s'>"!expr"</span> <span class='o'>=</span> \<span class='o'>(</span><span class='nv'>x</span><span class='o'>)</span> <span class='nf'><a href='https://rdrr.io/r/base/eval.html'>eval</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/parse.html'>str2expression</a></span><span class='o'>(</span><span class='nv'>x</span><span class='o'>)</span>, <span class='nf'><a href='https://rdrr.io/r/base/environment.html'>globalenv</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='s'>"!expr 1 + 1"</span>, handlers <span class='o'>=</span> <span class='nv'>handlers</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 2</span></span>
<span></span></code></pre>
</div>
<p>Python example:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'>from yaml12 import parse_yaml

handlers = {"!expr": eval}  # use with trusted input only
parse_yaml("!expr 1 + 1", handlers=handlers)

#> 2
</code></pre>
</div>
<h3 id="simplification-and-missing-values-r">Simplification and missing values (R)
</h3>
<p>In R, <a href="https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html" target="_blank" rel="noopener"><code>parse_yaml()</code></a>
 can simplify homogeneous sequences to vectors. When it does, YAML <code>null</code> becomes the appropriate <code>NA</code> type:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='s'>"[1, 2, 3, null]"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1]  1  2  3 NA</span></span>
<span></span><span></span>
<span><span class='nf'><a href='https://rdrr.io/r/utils/str.html'>str</a></span><span class='o'>(</span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='s'>"[1, 2, 3, null]"</span>, simplify <span class='o'>=</span> <span class='kc'>FALSE</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; List of 4</span></span>
<span><span class='c'>#&gt;  $ : int 1</span></span>
<span><span class='c'>#&gt;  $ : int 2</span></span>
<span><span class='c'>#&gt;  $ : int 3</span></span>
<span><span class='c'>#&gt;  $ : NULL</span></span>
<span></span></code></pre>
</div>
<h3 id="non-string-mapping-keys">Non-string mapping keys
</h3>
<p>YAML allows mapping keys that aren&rsquo;t plain strings (numbers, booleans, tagged scalars, even sequences and mappings). Both packages preserve these safely:</p>
<ul>
<li>In R, you&rsquo;ll get a regular named list plus a <code>yaml_keys</code> attribute when needed.</li>
<li>In Python, unhashable keys (like lists/dicts) are wrapped in <code>Yaml</code> so they can still be used as <code>dict</code> keys and round-trip correctly.</li>
</ul>
<p>R example:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/dput.html'>dput</a></span><span class='o'>(</span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='s'>"&#123;a: b&#125;: c"</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; structure(list("c"), names = "", yaml_keys = list(list(a = "b")))</span></span>
<span></span></code></pre>
</div>
<p>Python example:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'>from yaml12 import parse_yaml, Yaml

doc = parse_yaml("{a: b}: c")
assert doc == {Yaml({'a': 'b'}): 'c'}
</code></pre>
</div>
<h3 id="mapping-order-is-preserved">Mapping order is preserved
</h3>
<p>YAML mappings are ordered. <code>yaml12</code> preserves mapping/dictionary order when parsing and formatting, so the order you see in a YAML file (or emit) round-trips in both R and Python.</p>
<h3 id="document-streams-and-front-matter">Document streams and front matter
</h3>
<p>Both packages support multi-document YAML streams with <code>multi = TRUE</code>. When <code>multi = FALSE</code> (the default), parsing stops after the first document, which is handy for extracting YAML front matter from text that continues with non-YAML content.</p>
<p>Example:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>yaml</span> <span class='o'>&lt;-</span> <span class='s'>"</span></span>
<span><span class='s'>---</span></span>
<span><span class='s'>title: Extracting YAML front matter</span></span>
<span><span class='s'>---</span></span>
<span><span class='s'>This is technically now the second document in a YAML stream</span></span>
<span><span class='s'>"</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/utils/str.html'>str</a></span><span class='o'>(</span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='nv'>yaml</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; List of 1</span></span>
<span><span class='c'>#&gt;  $ title: chr "Extracting YAML front matter"</span></span>
<span></span><span><span class='nf'><a href='https://rdrr.io/r/utils/str.html'>str</a></span><span class='o'>(</span><span class='nf'><a href='https://posit-dev.github.io/r-yaml12/reference/parse_yaml.html'>parse_yaml</a></span><span class='o'>(</span><span class='nv'>yaml</span>, multi <span class='o'>=</span> <span class='kc'>TRUE</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; List of 2</span></span>
<span><span class='c'>#&gt;  $ :List of 1</span></span>
<span><span class='c'>#&gt;   ..$ title: chr "Extracting YAML front matter"</span></span>
<span><span class='c'>#&gt;  $ : chr "This is technically now the second document in a YAML stream"</span></span>
<span></span></code></pre>
</div>
<h3 id="performance-and-safety-notes">Performance and safety notes
</h3>
<p><code>yaml12</code> is implemented in Rust and written with performance and safety in mind. It avoids unnecessary allocations, copies, and extra traversals where possible. In Python, <code>py-yaml12</code> (imported as <code>yaml12</code>) also releases the GIL for large parses and serializations.</p>
<p>In typical usage, the R package <code>yaml12</code> is ~2× faster than the <code>yaml</code> package, and the Python package <code>py-yaml12</code> is ≥50× faster than default <code>PyYAML</code> in the benchmarks (<a href="https://posit-dev.github.io/r-yaml12/articles/benchmarks.html" target="_blank" rel="noopener">R benchmarks</a>
; <a href="https://posit-dev.github.io/py-yaml12/benchmarks/#read-performance" target="_blank" rel="noopener">Python benchmarks</a>
).</p>
<p>Tags are preserved by default, and interpreting them (including any kind of evaluation) is always an explicit opt-in via handlers. Plain scalars follow the YAML 1.2 core schema rules for predictable typing.</p>
<p>In Python, <code>py-yaml12</code> ships prebuilt wheels for common platforms. If you do need to build from source, you&rsquo;ll need a Rust toolchain. In R, <code>yaml12</code> is available from CRAN (including binaries on common platforms).</p>
<h2 id="wrapping-up">Wrapping up
</h2>
<p>If you work with YAML as a data format for configuration, front matter, or data interchange, we hope <code>yaml12</code> (R) and <code>py-yaml12</code> (Python) help you parse and emit YAML 1.2 predictably. If you run into YAML that doesn&rsquo;t behave as expected, we&rsquo;d love to hear about it in the issue trackers: <a href="https://github.com/posit-dev/r-yaml12/issues" target="_blank" rel="noopener">r-yaml12</a>
 and <a href="https://github.com/posit-dev/py-yaml12/issues" target="_blank" rel="noopener">py-yaml12</a>
.</p>
<h2 id="learn-more">Learn more
</h2>
<ul>
<li>R package docs: <a href="https://posit-dev.github.io/r-yaml12/" target="_blank" rel="noopener">https://posit-dev.github.io/r-yaml12/</a>
</li>
<li>R package on CRAN: <a href="https://cran.r-project.org/package=yaml12" target="_blank" rel="noopener">https://cran.r-project.org/package=yaml12</a>
</li>
<li>Python package docs: <a href="https://posit-dev.github.io/py-yaml12/" target="_blank" rel="noopener">https://posit-dev.github.io/py-yaml12/</a>
</li>
<li>Python package on PyPI: <a href="https://pypi.org/project/py-yaml12/" target="_blank" rel="noopener">https://pypi.org/project/py-yaml12/</a>
</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>Both packages build on the fantastic work in the YAML ecosystem, especially the <code>saphyr</code> Rust crate and the <a href="https://github.com/yaml/yaml-test-suite" target="_blank" rel="noopener">yaml-test-suite</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/yaml12-0-1-0/thumbnail-wd.jpg" length="477241" type="image/jpeg" />
    </item>
    <item>
      <title>testthat 3.3.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/testthat-3-3-0/</link>
      <pubDate>Thu, 13 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/testthat-3-3-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re chuffed to announce the release of <a href="https://testthat.r-lib.org" target="_blank" rel="noopener">testthat</a>
 3.3.0. testthat is a testing framework for R that makes it easy to turn your existing informal tests into formal, automated tests that you can rerun quickly and easily.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"testthat"</span><span class='o'>)</span></span></code></pre>
</div>
<p>This blog post highlights the most important changes in this release, including lifecycle changes that removed long-deprecated mocking functions, improvements to expectations and their error messages, and a variety of new features that make testing easier and more robust. You can see a full list of changes in the <a href="https://github.com/r-lib/testthat/releases/tag/v3.3.0" target="_blank" rel="noopener">release notes</a>
.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://testthat.r-lib.org'>testthat</a></span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="claude-code-experiences">Claude Code experiences
</h2>
<p>Before we dive into the changes, I wanted to talk a little bit about some changes to my development process, as I used this release as an opportunity to learn <a href="https://www.claude.com/product/claude-code" target="_blank" rel="noopener">Claude Code</a>
. This is the first package where I&rsquo;ve really used AI to support the development of many features and I thought it might be useful to share my experience.</p>
<p>Overall it was a successful experiment. It helped me close over 100 issues in what felt like less time than usual. I don&rsquo;t have any hard numbers, but my gut feeling is that it was maybe a 10-20% improvement to my development velocity. This is still significant, especially since I&rsquo;m an experienced R programmer and my workflow has been pretty stable for the last few years. I mostly used Claude for smaller, well-defined tasks where I had a good sense of what was needed. I found it particularly useful for refactoring, where it was easy to say precisely what I wanted, but executing the changes required a bunch of fiddly edits across many files.</p>
<p>I also found it generally useful for getting over the &ldquo;activation energy hump&rdquo;: there were a few issues that had been stagnating for years because they felt like they were going to be hard to do and with relatively limited payoff. I let Claude Code loose on a few of these and found it super useful. It only produced code I was really happy with a couple of times, but every time it gave me something to react to (often with strong negative feelings!) and that got me started actually engaging with the problem.</p>
<p>If you&rsquo;re interested in using Claude Code yourself, there are a couple of files you might find useful. My <a href="https://github.com/r-lib/testthat/blob/main/.claude/CLAUDE.md" target="_blank" rel="noopener"><code>CLAUDE.md</code></a>
 tells Claude how to execute a devtools-based workflow, along with a few pointers to resolve common issues. My <a href="https://github.com/r-lib/testthat/blob/main/.claude/settings.json" target="_blank" rel="noopener"><code>settings.json</code></a>
 allows Claude to run longer without human intervention, doing things that should mostly be safe. One note of caution: these settings do allow Claude to run R code, which does allow it to do practically anything. In my experience, Claude only used R to run tests or documentation.</p>
<p>I also experimented with using Claude Code to review PRs. It was just barely useful enough that I kept it turned on for my own PRs, but I didn&rsquo;t bother trying to get it to work for contributed PRs. Most of the time it either gave a thumbs up or bad advice, but every now and then it would pick up a small error.</p>
<p>(I&rsquo;ve also used Claude Code to proofread this blog post!)</p>
<h2 id="lifecycle-changes">Lifecycle changes
</h2>
<p>The biggest change in this release is that <a href="https://testthat.r-lib.org/reference/with_mock.html" target="_blank" rel="noopener"><code>local_mock()</code></a>
 and <a href="https://testthat.r-lib.org/reference/with_mock.html" target="_blank" rel="noopener"><code>with_mock()</code></a>
 are defunct. They were deprecated in 3.0.0 (2020-10-31) because it was becoming clear that the technique that made them work would be disallowed in a future version of R. This has now happened in R 4.5.0, so the functions have been removed. Removing <a href="https://testthat.r-lib.org/reference/with_mock.html" target="_blank" rel="noopener"><code>local_mock()</code></a>
 and <a href="https://testthat.r-lib.org/reference/with_mock.html" target="_blank" rel="noopener"><code>with_mock()</code></a>
 was a fairly disruptive change, affecting ~100 CRAN packages, but it had to be done, and I&rsquo;ve been working on notifying package developers since January so everyone had plenty of time to update. Fortunately, the needed changes are generally small, since the newer <a href="https://testthat.r-lib.org/reference/local_mocked_bindings.html" target="_blank" rel="noopener"><code>local_mocked_bindings()</code></a>
 and <a href="https://testthat.r-lib.org/reference/local_mocked_bindings.html" target="_blank" rel="noopener"><code>with_mocked_bindings()</code></a>
 can solve most additional needs. (If you haven&rsquo;t heard of mocking before, you can read the new <code>vignette(&quot;mocking&quot;)</code> to learn what it is and why you might want to use it.)</p>
<p>Other lifecycle changes:</p>
<ul>
<li>
<p>testthat now requires R 4.1. This follows <a href="https://tidyverse.org/blog/2019/04/r-version-support/" target="_blank" rel="noopener">our supported version policy</a>
, which documents our commitment to support five versions of R (the current version and four previous versions). We&rsquo;re excited to be able to finally take advantage of the base pipe and compact anonymous functions (i.e. <code>\(x) x + 1</code>)!</p>
</li>
<li>
<p><code>is_null()</code>/<code>matches()</code>, deprecated in 2.0.0 (2017-12-19), and <code>is_true()</code>/<code>is_false()</code>, deprecated in 2.1.0 (2019-04-23), have been removed. These conflicted with other tidyverse functions so we pushed their deprecation through, even though we have generally left the old <a href="https://testthat.r-lib.org/reference/test_that.html" target="_blank" rel="noopener"><code>test_that()</code></a>
 API untouched.</p>
</li>
<li>
<p><code>expect_snapshot(binary)</code>, soft deprecated in 3.0.3 (2021-06-16), is now fully deprecated. <code>test_files(wrap)</code>, deprecated in 3.0.0 (2020-10-31), has now been removed.</p>
</li>
<li>
<p>There were a few other changes that broke existing packages. The most impactful change was to start checking the inputs to <a href="https://testthat.r-lib.org/reference/expect.html" target="_blank" rel="noopener"><code>expect()</code></a>
 which, despite the name, is actually an internal helper. That revealed a surprising number of packages were accidentally using <a href="https://testthat.r-lib.org/reference/expect.html" target="_blank" rel="noopener"><code>expect()</code></a>
 instead of <a href="https://testthat.r-lib.org/reference/logical-expectations.html" target="_blank" rel="noopener"><code>expect_true()</code></a>
 or <a href="https://testthat.r-lib.org/reference/equality-expectations.html" target="_blank" rel="noopener"><code>expect_equal()</code></a>
. We don&rsquo;t technically consider this a breaking change because it revealed off-label function usage: the function API hasn&rsquo;t changed; you just now learn when you&rsquo;re using it incorrectly.</p>
</li>
</ul>
<p>If you&rsquo;re interested in the process we use to manage the release of a package that breaks its reverse dependencies, you might like to read <a href="https://github.com/r-lib/testthat/issues/2021" target="_blank" rel="noopener">the issue</a>
 where I track all the problems and prepare PRs to fix them.</p>
<h2 id="expectations-and-the-interactive-testing-experience">Expectations and the interactive testing experience
</h2>
<p>A lot of work in this release was prompted by an overhaul of <code>vignette(&quot;custom-expectations&quot;)</code>, which describes how to create your own expectations that work just like testthat&rsquo;s. This is a long time coming, and as I was working on it, I realized that I didn&rsquo;t really know how to write new expectations, which had led to a lot of variation in the existing implementations. This kicked off a bunch of experimentation and iterating, leading to a swath of improvements:</p>
<ul>
<li>
<p>All expectations have new failure messages: they now state what was expected, what was actually received, and, if possible, they clearly illustrate the difference.</p>
</li>
<li>
<p>Expectations now consistently return the value of the first argument, regardless of whether the expectation succeeds or fails (the only exception is <a href="https://testthat.r-lib.org/reference/expect_error.html" target="_blank" rel="noopener"><code>expect_error()</code></a>
 and friends which return the captured condition so that you can perform additional checks on the condition object). This is a relatively subtle change that won&rsquo;t affect tests that already pass, but it does improve failures when you pipe together multiple expectations.</p>
</li>
<li>
<p>A new <a href="https://testthat.r-lib.org/reference/fail.html" target="_blank" rel="noopener"><code>pass()</code></a>
 function makes it clear how to signal when an expectation succeeds. All existing expectations were rewritten to use <a href="https://testthat.r-lib.org/reference/fail.html" target="_blank" rel="noopener"><code>pass()</code></a>
 and (the existing) <a href="https://testthat.r-lib.org/reference/fail.html" target="_blank" rel="noopener"><code>fail()</code></a>
 instead of <a href="https://testthat.r-lib.org/reference/expect.html" target="_blank" rel="noopener"><code>expect()</code></a>
, which I think makes the flow of logic easier to understand.</p>
</li>
<li>
<p>Improved <a href="https://testthat.r-lib.org/reference/expect_success.html" target="_blank" rel="noopener"><code>expect_success()</code></a>
 and <a href="https://testthat.r-lib.org/reference/expect_success.html" target="_blank" rel="noopener"><code>expect_failure()</code></a>
 expectations now test that an expectation always returns exactly one success or failure (this ensures that the counts that you see in the reporters are correct).</p>
</li>
</ul>
<p>This new framework helped us write six new expectations:</p>
<ul>
<li>
<p><a href="https://testthat.r-lib.org/reference/expect_all_equal.html" target="_blank" rel="noopener"><code>expect_all_equal()</code></a>
, <a href="https://testthat.r-lib.org/reference/expect_all_equal.html" target="_blank" rel="noopener"><code>expect_all_true()</code></a>
, and <a href="https://testthat.r-lib.org/reference/expect_all_equal.html" target="_blank" rel="noopener"><code>expect_all_false()</code></a>
 check that every element of a vector has the same value, giving better error messages than <code>expect_true(all(...))</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"some test"</span>, <span class='o'>&#123;</span></span>
<span>  <span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='m'>0.408</span>, <span class='m'>0.961</span>, <span class='m'>0.883</span>, <span class='m'>0.46</span>, <span class='m'>0.537</span>, <span class='m'>0.961</span>, <span class='m'>0.851</span>, <span class='m'>0.887</span>, <span class='m'>0.023</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/expect_all_equal.html'>expect_all_true</a></span><span class='o'>(</span><span class='nv'>x</span> <span class='o'>&lt;</span> <span class='m'>0.95</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: some test</span> ────────────────────────────────────────────────</span></span>
<span><span class='c'>#&gt; Expected every element of `x &lt; 0.95` to equal TRUE.</span></span>
<span><span class='c'>#&gt; Differences:</span></span>
<span><span class='c'>#&gt; `actual`:   <span style='color: #555555;'>TRUE</span> <span style='color: #00BB00;'>FALSE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #00BB00;'>FALSE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span></span></span>
<span><span class='c'>#&gt; `expected`: <span style='color: #555555;'>TRUE</span> <span style='color: #00BB00;'>TRUE</span>  <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #00BB00;'>TRUE</span>  <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span> <span style='color: #555555;'>TRUE</span></span></span>
<span></span><span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> Test failed with 1 failure and 0 successes.</span></span>
<span></span></code></pre>
</div>
</li>
<li>
<p><a href="https://testthat.r-lib.org/reference/expect_setequal.html" target="_blank" rel="noopener"><code>expect_disjoint()</code></a>
, by <a href="https://github.com/stibu81" target="_blank" rel="noopener">@stibu81</a>
, expects values to be absent:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>""</span>, <span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/expect_setequal.html'>expect_disjoint</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"a"</span>, <span class='s'>"b"</span>, <span class='s'>"c"</span><span class='o'>)</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"c"</span>, <span class='s'>"d"</span>, <span class='s'>"e"</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: </span> ─────────────────────────────────────────────────────────</span></span>
<span><span class='c'>#&gt; Expected `c("a", "b", "c")` to be disjoint from `c("c", "d", "e")`.</span></span>
<span><span class='c'>#&gt; Actual: "a", "b", "c"</span></span>
<span><span class='c'>#&gt; Expected: None of "c", "d", "e"</span></span>
<span><span class='c'>#&gt; Invalid: "c"</span></span>
<span></span><span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> Test failed with 1 failure and 0 successes.</span></span>
<span></span></code></pre>
</div>
</li>
<li>
<p><a href="https://testthat.r-lib.org/reference/inheritance-expectations.html" target="_blank" rel="noopener"><code>expect_r6_class()</code></a>
 expects an R6 object:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>""</span>, <span class='o'>&#123;</span></span>
<span>  <span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='m'>10</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/inheritance-expectations.html'>expect_r6_class</a></span><span class='o'>(</span><span class='nv'>x</span>, <span class='s'>"foo"</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='nf'>R6</span><span class='nf'>::</span><span class='kr'><a href='https://r6.r-lib.org/reference/R6Class.html'>R6Class</a></span><span class='o'>(</span><span class='s'>"bar"</span><span class='o'>)</span><span class='o'>$</span><span class='nf'>new</span><span class='o'>(</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/inheritance-expectations.html'>expect_r6_class</a></span><span class='o'>(</span><span class='nv'>x</span>, <span class='s'>"foo"</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: </span> ─────────────────────────────────────────────────────────</span></span>
<span><span class='c'>#&gt; Expected `x` to be an R6 object.</span></span>
<span><span class='c'>#&gt; Actual OO type: none.</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: </span> ─────────────────────────────────────────────────────────</span></span>
<span><span class='c'>#&gt; Expected `x` to inherit from "foo".</span></span>
<span><span class='c'>#&gt; Actual class: "bar"/"R6".</span></span>
<span></span><span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> Test failed with 2 failures and 0 successes.</span></span>
<span></span></code></pre>
</div>
</li>
<li>
<p><a href="https://testthat.r-lib.org/reference/expect_length.html" target="_blank" rel="noopener"><code>expect_shape()</code></a>
, by <a href="https://github.com/michaelchirico" target="_blank" rel="noopener">@michaelchirico</a>
, expects a specific shape (i.e., <a href="https://rdrr.io/r/base/nrow.html" target="_blank" rel="noopener"><code>nrow()</code></a>
, <a href="https://rdrr.io/r/base/nrow.html" target="_blank" rel="noopener"><code>ncol()</code></a>
, or <a href="https://rdrr.io/r/base/dim.html" target="_blank" rel="noopener"><code>dim()</code></a>
):</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"show off expect_shape() failure messages"</span>, <span class='o'>&#123;</span></span>
<span>  <span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/matrix.html'>matrix</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>:</span><span class='m'>9</span>, nrow <span class='o'>=</span> <span class='m'>3</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/expect_length.html'>expect_shape</a></span><span class='o'>(</span><span class='nv'>x</span>, nrow <span class='o'>=</span> <span class='m'>4</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/expect_length.html'>expect_shape</a></span><span class='o'>(</span><span class='nv'>x</span>, dim <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='m'>3</span>, <span class='m'>3</span>, <span class='m'>3</span><span class='o'>)</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/expect_length.html'>expect_shape</a></span><span class='o'>(</span><span class='nv'>x</span>, dim <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='m'>3</span>, <span class='m'>4</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: show off expect_shape() failure messages</span> ─────────────────</span></span>
<span><span class='c'>#&gt; Expected `x` to have 4 rows.</span></span>
<span><span class='c'>#&gt; Actual rows: 3.</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: show off expect_shape() failure messages</span> ─────────────────</span></span>
<span><span class='c'>#&gt; Expected `x` to have 3 dimensions.</span></span>
<span><span class='c'>#&gt; Actual dimensions: 2.</span></span>
<span><span class='c'>#&gt; ── <span style='color: #BBBB00; font-weight: bold;'>Failure</span><span style='font-weight: bold;'>: show off expect_shape() failure messages</span> ─────────────────</span></span>
<span><span class='c'>#&gt; Expected `x` to have dim (3, 4).</span></span>
<span><span class='c'>#&gt; Actual dim: (3, 3).</span></span>
<span></span><span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> Test failed with 3 failures and 0 successes.</span></span>
<span></span></code></pre>
</div>
</li>
</ul>
<p>As you can see from the examples above, when you run a single test interactively (i.e. not as a part of a test suite) you now see exactly how many expectations succeeded and failed.</p>
<h2 id="other-new-features">Other new features
</h2>
<ul>
<li>
<p>testthat generally does a better job of handling nested tests, aka subtests, where you put a <a href="https://testthat.r-lib.org/reference/test_that.html" target="_blank" rel="noopener"><code>test_that()</code></a>
 inside another <a href="https://testthat.r-lib.org/reference/test_that.html" target="_blank" rel="noopener"><code>test_that()</code></a>
, or more typically <a href="https://testthat.r-lib.org/reference/describe.html" target="_blank" rel="noopener"><code>it()</code></a>
 inside of <a href="https://testthat.r-lib.org/reference/describe.html" target="_blank" rel="noopener"><code>describe()</code></a>
. Subtests will now generate more informative failure messages, free from duplication, with more informative skips if any subtests don&rsquo;t contain any expectations.</p>
</li>
<li>
<p>The snapshot experience has been significantly improved, with all known bugs fixed and some new helpers added: <a href="https://testthat.r-lib.org/reference/snapshot_accept.html" target="_blank" rel="noopener"><code>snapshot_reject()</code></a>
 rejects all modified snapshots by deleting the <code>.new</code> variants, and <a href="https://testthat.r-lib.org/reference/snapshot_download_gh.html" target="_blank" rel="noopener"><code>snapshot_download_gh()</code></a>
 makes it easy to get snapshots off GitHub and into your local package. Additionally, <a href="https://testthat.r-lib.org/reference/expect_snapshot.html" target="_blank" rel="noopener"><code>expect_snapshot()</code></a>
 and friends will now fail when creating a new snapshot on CI, as that&rsquo;s usually a signal that you&rsquo;ve forgotten to run the snapshot code locally before committing.</p>
</li>
<li>
<p>On CRAN, <a href="https://testthat.r-lib.org/reference/test_that.html" target="_blank" rel="noopener"><code>test_that()</code></a>
 will automatically skip if a package is not installed, which means that you no longer need to check if suggested packages are installed in your tests.</p>
</li>
<li>
<p><code>vignette(&quot;mocking&quot;)</code> explains mocking in detail, and new <a href="https://testthat.r-lib.org/reference/local_mocked_s3_method.html" target="_blank" rel="noopener"><code>local_mocked_s3_method()</code></a>
, <a href="https://testthat.r-lib.org/reference/local_mocked_s3_method.html" target="_blank" rel="noopener"><code>local_mocked_s4_method()</code></a>
, and <a href="https://testthat.r-lib.org/reference/local_mocked_r6_class.html" target="_blank" rel="noopener"><code>local_mocked_r6_class()</code></a>
 make it easier to mock S3 and S4 methods and R6 classes.</p>
</li>
<li>
<p><a href="https://testthat.r-lib.org/reference/test_dir.html" target="_blank" rel="noopener"><code>test_dir()</code></a>
, <a href="https://testthat.r-lib.org/reference/test_package.html" target="_blank" rel="noopener"><code>test_check()</code></a>
, and friends gain a <code>shuffle</code> argument that uses <a href="https://rdrr.io/r/base/sample.html" target="_blank" rel="noopener"><code>sample()</code></a>
 to randomly reorder the top-level expressions in each test file. This random reordering surfaces dependencies between tests and code outside of any test, as well as dependencies between tests, helping you find and eliminate unintentional dependencies.</p>
</li>
<li>
<p><a href="https://testthat.r-lib.org/reference/try_again.html" target="_blank" rel="noopener"><code>try_again()</code></a>
 is now publicized, as it&rsquo;s a useful tool for testing flaky code:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>flaky_function</span> <span class='o'>&lt;-</span> <span class='kr'>function</span><span class='o'>(</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
  <span>  <span class='kr'>if</span> <span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/stats/Uniform.html'>runif</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>)</span> <span class='o'>&lt;</span> <span class='m'>0.1</span><span class='o'>)</span> <span class='m'>0</span> <span class='kr'>else</span> <span class='m'>1</span></span>
  <span><span class='o'>&#125;</span></span>
  <span></span>
  <span><span class='c'># 10% chance of failure:</span></span>
  <span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"my flaky test is ok"</span>, <span class='o'>&#123;</span></span>
  <span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/skip.html'>skip_on_cran</a></span><span class='o'>(</span><span class='o'>)</span></span>
  <span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/equality-expectations.html'>expect_equal</a></span><span class='o'>(</span><span class='nf'>flaky_function</span><span class='o'>(</span><span class='o'>)</span>, <span class='m'>1</span><span class='o'>)</span></span>
  <span><span class='o'>&#125;</span><span class='o'>)</span></span>
  <span></span>
  <span><span class='c'># 1% chance of failure:</span></span>
  <span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"my flaky test is ok"</span>, <span class='o'>&#123;</span></span>
  <span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/skip.html'>skip_on_cran</a></span><span class='o'>(</span><span class='o'>)</span></span>
  <span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/try_again.html'>try_again</a></span><span class='o'>(</span><span class='m'>1</span>, <span class='nf'><a href='https://testthat.r-lib.org/reference/equality-expectations.html'>expect_equal</a></span><span class='o'>(</span><span class='nf'>flaky_function</span><span class='o'>(</span><span class='o'>)</span>, <span class='m'>1</span><span class='o'>)</span><span class='o'>)</span></span>
  <span><span class='o'>&#125;</span><span class='o'>)</span></span>
  <span></span>
  <span><span class='c'># 0.1% chance of failure:</span></span>
  <span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"my flaky test is ok"</span>, <span class='o'>&#123;</span></span>
  <span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/skip.html'>skip_on_cran</a></span><span class='o'>(</span><span class='o'>)</span></span>
  <span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/try_again.html'>try_again</a></span><span class='o'>(</span><span class='m'>2</span>, <span class='nf'><a href='https://testthat.r-lib.org/reference/equality-expectations.html'>expect_equal</a></span><span class='o'>(</span><span class='nf'>flaky_function</span><span class='o'>(</span><span class='o'>)</span>, <span class='m'>1</span><span class='o'>)</span><span class='o'>)</span></span>
  <span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>Note that it&rsquo;s still good practice to skip such tests on CRAN.</p>
</li>
<li>
<p>New <a href="https://testthat.r-lib.org/reference/skip.html" target="_blank" rel="noopener"><code>skip_unless_r()</code></a>
 skips tests on unsuitable versions of R. It has a convenient syntax so you can use, e.g., <code>skip_unless_r(&quot;&gt;= 4.1.0&quot;)</code> to skip tests that require <a href="https://rdrr.io/r/base/dots.html" target="_blank" rel="noopener"><code>...names()</code></a>
.</p>
</li>
<li>
<p>New <code>SlowReporter</code> makes it easier to find the slowest tests in your package. You can run it with <code>devtools::test(reporter = &quot;slow&quot;)</code>.</p>
</li>
<li>
<p>New <code>vignette(&quot;challenging-functions&quot;)</code> provides an index to other documentation organized by various challenges.</p>
</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to all the folks who helped make this release happen: <a href="https://github.com/3styleJam" target="_blank" rel="noopener">@3styleJam</a>
, <a href="https://github.com/afinez" target="_blank" rel="noopener">@afinez</a>
, <a href="https://github.com/andybeet" target="_blank" rel="noopener">@andybeet</a>
, <a href="https://github.com/atheriel" target="_blank" rel="noopener">@atheriel</a>
, <a href="https://github.com/averissimo" target="_blank" rel="noopener">@averissimo</a>
, <a href="https://github.com/d-morrison" target="_blank" rel="noopener">@d-morrison</a>
, <a href="https://github.com/DanChaltiel" target="_blank" rel="noopener">@DanChaltiel</a>
, <a href="https://github.com/DanielHermosilla" target="_blank" rel="noopener">@DanielHermosilla</a>
, <a href="https://github.com/eitsupi" target="_blank" rel="noopener">@eitsupi</a>
, <a href="https://github.com/EmilHvitfeldt" target="_blank" rel="noopener">@EmilHvitfeldt</a>
, <a href="https://github.com/emstruong" target="_blank" rel="noopener">@emstruong</a>
, <a href="https://github.com/gaborcsardi" target="_blank" rel="noopener">@gaborcsardi</a>
, <a href="https://github.com/gael-millot" target="_blank" rel="noopener">@gael-millot</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/hoeflerb" target="_blank" rel="noopener">@hoeflerb</a>
, <a href="https://github.com/jamesfowkes" target="_blank" rel="noopener">@jamesfowkes</a>
, <a href="https://github.com/jan-swissre" target="_blank" rel="noopener">@jan-swissre</a>
, <a href="https://github.com/jdblischak" target="_blank" rel="noopener">@jdblischak</a>
, <a href="https://github.com/jennybc" target="_blank" rel="noopener">@jennybc</a>
, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>
, <a href="https://github.com/kevinushey" target="_blank" rel="noopener">@kevinushey</a>
, <a href="https://github.com/krivit" target="_blank" rel="noopener">@krivit</a>
, <a href="https://github.com/kubajal" target="_blank" rel="noopener">@kubajal</a>
, <a href="https://github.com/lawalter" target="_blank" rel="noopener">@lawalter</a>
, <a href="https://github.com/m-muecke" target="_blank" rel="noopener">@m-muecke</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/math-mcshane" target="_blank" rel="noopener">@math-mcshane</a>
, <a href="https://github.com/mcol" target="_blank" rel="noopener">@mcol</a>
, <a href="https://github.com/metanoid" target="_blank" rel="noopener">@metanoid</a>
, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>
, <a href="https://github.com/moodymudskipper" target="_blank" rel="noopener">@moodymudskipper</a>
, <a href="https://github.com/njtierney" target="_blank" rel="noopener">@njtierney</a>
, <a href="https://github.com/nunotexbsd" target="_blank" rel="noopener">@nunotexbsd</a>
, <a href="https://github.com/pabangan" target="_blank" rel="noopener">@pabangan</a>
, <a href="https://github.com/pachadotdev" target="_blank" rel="noopener">@pachadotdev</a>
, <a href="https://github.com/plietar" target="_blank" rel="noopener">@plietar</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/schuemie" target="_blank" rel="noopener">@schuemie</a>
, <a href="https://github.com/sebkopf" target="_blank" rel="noopener">@sebkopf</a>
, <a href="https://github.com/shikokuchuo" target="_blank" rel="noopener">@shikokuchuo</a>
, <a href="https://github.com/snystrom" target="_blank" rel="noopener">@snystrom</a>
, <a href="https://github.com/stibu81" target="_blank" rel="noopener">@stibu81</a>
, <a href="https://github.com/TimTaylor" target="_blank" rel="noopener">@TimTaylor</a>
, and <a href="https://github.com/tylermorganwall" target="_blank" rel="noopener">@tylermorganwall</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/testthat-3-3-0/thumbnail-wd.jpg" length="116709" type="image/jpeg" />
    </item>
    <item>
      <title>pkgdown 2.2.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/pkgdown-2-2-0/</link>
      <pubDate>Thu, 06 Nov 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/pkgdown-2-2-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re delighted to announce the release of <a href="https://pkgdown.r-lib.org" target="_blank" rel="noopener">pkgdown</a>
 2.2.0. pkgdown is designed to make it quick and easy to build a beautiful and accessible website for your package.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"pkgdown"</span><span class='o'>)</span></span></code></pre>
</div>
<p>This version of pkgdown has one major change: a new <a href="https://pkgdown.r-lib.org/reference/build_llm_docs.html" target="_blank" rel="noopener"><code>pkgdown::build_llm_docs()</code></a>
 function that automatically creates files that make it easier for LLMs to read your documentation. Concretely, this means two things:</p>
<ul>
<li>
<p>You&rsquo;ll get an <code>llms.txt</code> at the root directory of your site. <a href="https://llmstxt.org" target="_blank" rel="noopener"><code>llms.txt</code></a>
 is an emerging standard that provides an easy way for an LLM to get an overview of your site. pkgdown creates an overview by combining your README, your function index, and your article index: this should give the LLM a broad overview of what your package does, along with links to find out more.</p>
</li>
<li>
<p>Every existing <code>.html</code> on your site gets a corresponding <code>.md</code> file. These are generally easier for LLMs to understand because they contain just the content of the site, without any extraneous styling.</p>
</li>
</ul>
<p>If you don&rsquo;t want to generate these files, just add the following to your <code>_pkgdown.yaml</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'>llm-docs: false
</code></pre>
</div>
<p>This release also includes new translations for Dutch and Japanese, removal of the long-deprecated <code>autolink_html()</code> and <code>preview_page()</code>, and a handful of other bug fixes and minor improvements. You can read about them all in the <a href="https://github.com/r-lib/pkgdown/releases/tag/v2.2.0" target="_blank" rel="noopener">release notes</a>
.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>As always, a big thanks to everyone who helped make this release possible: <a href="https://github.com/cderv" target="_blank" rel="noopener">@cderv</a>
, <a href="https://github.com/chabld" target="_blank" rel="noopener">@chabld</a>
, <a href="https://github.com/Danny-dK" target="_blank" rel="noopener">@Danny-dK</a>
, <a href="https://github.com/davidorme" target="_blank" rel="noopener">@davidorme</a>
, <a href="https://github.com/dmurdoch" target="_blank" rel="noopener">@dmurdoch</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/hfrick" target="_blank" rel="noopener">@hfrick</a>
, <a href="https://github.com/IndrajeetPatil" target="_blank" rel="noopener">@IndrajeetPatil</a>
, <a href="https://github.com/jayhesselberth" target="_blank" rel="noopener">@jayhesselberth</a>
, <a href="https://github.com/jeroenjanssens" target="_blank" rel="noopener">@jeroenjanssens</a>
, <a href="https://github.com/jmgirard" target="_blank" rel="noopener">@jmgirard</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/lorenzwalthert" target="_blank" rel="noopener">@lorenzwalthert</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>
, <a href="https://github.com/pepijn-devries" target="_blank" rel="noopener">@pepijn-devries</a>
, <a href="https://github.com/remlapmot" target="_blank" rel="noopener">@remlapmot</a>
, <a href="https://github.com/rempsyc" target="_blank" rel="noopener">@rempsyc</a>
, <a href="https://github.com/Rohit-Satyam" target="_blank" rel="noopener">@Rohit-Satyam</a>
, <a href="https://github.com/royfrancis" target="_blank" rel="noopener">@royfrancis</a>
, <a href="https://github.com/rparmm" target="_blank" rel="noopener">@rparmm</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/TimTaylor" target="_blank" rel="noopener">@TimTaylor</a>
, and <a href="https://github.com/usrbinr" target="_blank" rel="noopener">@usrbinr</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/pkgdown-2-2-0/thumbnail-wd.jpg" length="353997" type="image/jpeg" />
    </item>
    <item>
      <title>mirai 2.5.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/mirai-2-5-0/</link>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/mirai-2-5-0/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re excited to announce <a href="https://mirai.r-lib.org" target="_blank" rel="noopener">mirai</a>
 2.5.0, bringing production-grade async computing to R!</p>
<p>This milestone release delivers enhanced observability through OpenTelemetry, reproducible parallel RNG, and key user interface improvements. We&rsquo;ve also packed in twice as many <a href="https://mirai.r-lib.org/news/index.html" target="_blank" rel="noopener">changes</a>
 as usual - going all out in delivering a round of quality-of-life fixes to make your use of mirai even smoother!</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"mirai"</span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="introduction-to-mirai">Introduction to mirai
</h2>
<p>mirai (Japanese for &lsquo;future&rsquo;) provides a clean, modern approach to parallel computing in R. Built on current communication technologies, it delivers extreme performance through professional-grade scheduling and an event-driven architecture.</p>
<p>It continues to evolve as the foundation for asynchronous and parallel computing across the R ecosystem, powering everything from <a href="https://rstudio.github.io/promises/articles/promises_04_mirai.html" target="_blank" rel="noopener">async Shiny</a>
 applications to <a href="https://www.tidyverse.org/blog/2025/07/purrr-1-1-0-parallel/" target="_blank" rel="noopener">parallel map</a>
 in purrr to <a href="https://tune.tidymodels.org/news/index.html#parallel-processing-2-0-0" target="_blank" rel="noopener">hyperparameter tuning</a>
 in tidymodels.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://mirai.r-lib.org'>mirai</a></span><span class='o'>)</span></span>
<span></span>
<span><span class='c'># Set up persistent background processes</span></span>
<span><span class='nf'><a href='https://mirai.r-lib.org/reference/daemons.html'>daemons</a></span><span class='o'>(</span><span class='m'>4</span><span class='o'>)</span></span>
<span></span>
<span><span class='c'># Async evaluation - non-blocking</span></span>
<span><span class='nv'>m</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://mirai.r-lib.org/reference/mirai.html'>mirai</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/Sys.sleep.html'>Sys.sleep</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>)</span></span>
<span>  <span class='m'>100</span> <span class='o'>+</span> <span class='m'>42</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='nv'>m</span></span>
<span><span class='c'>#&gt; &lt; mirai [] &gt;</span></span>
<span></span><span></span>
<span><span class='c'># Results are available when ready</span></span>
<span><span class='nv'>m</span><span class='o'>[</span><span class='o'>]</span></span>
<span><span class='c'>#&gt; [1] 142</span></span>
<span></span><span></span>
<span><span class='c'># Shut down persistent background processes</span></span>
<span><span class='nf'><a href='https://mirai.r-lib.org/reference/daemons.html'>daemons</a></span><span class='o'>(</span><span class='m'>0</span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="a-unique-design-philosophy">A unique design philosophy
</h2>
<p><strong>Modern foundation</strong>: mirai builds on <a href="https://nanonext.r-lib.org" target="_blank" rel="noopener">nanonext</a>
, the R binding to Nanomsg Next Generation, a high-performance messaging library designed for distributed systems. This means that it&rsquo;s using the very latest technologies, and supports the most optimal connections out of the box: IPC (inter-process communications), TCP or secure TLS. It also extends base R&rsquo;s serialization mechanism to support custom serialization of newer cross-language data formats such as safetensors, Arrow and Polars.</p>
<p><strong>Extreme performance</strong>: as a consequence of its solid technological foundation, mirai has the proven capacity to scale to millions of concurrent tasks over thousands of connections. Moreover, it delivers up to 1,000x the efficiency and responsiveness of other alternatives. A key innovation is the implementation of event-driven promises that react with zero latency - this provides an extra edge for real-time applications such as live inference or Shiny apps.</p>
<p><strong>Production first</strong>: mirai provides a clear mental model for parallel computation, with a clean separation of a user&rsquo;s current environment with that in which a mirai is evaluated. This explicitness and simplicity helps avoid common pitfalls that can afflict parallel processing, such as capturing incorrect or extraneous variables. Transparency and robustness are key to mirai&rsquo;s design, and are achieved by minimizing complexity, and eliminating all hidden state with no reliance on options or environment variables. Finally, its integration with OpenTelemetry provides for production-grade observability.</p>
<p><strong>Deploy everywhere</strong>: deployment of daemon processes is made through a consistent interface across local, remote (SSH), and <a href="https://shikokuchuo.net/posts/27-mirai-240/" target="_blank" rel="noopener">HPC environments</a>
 (Slurm, SGE, PBS, LSF). Compute profiles are daemons settings that are managed independently, such that you can be connected to all three resource types simultaneously. You then have the freedom to distribute workload to the most appropriate resource for any given task - especially important if tasks have differing requirements such as GPU compute.</p>
<h2 id="opentelemetry-integration">OpenTelemetry integration
</h2>
<p>New in mirai 2.5.0: complete observability of mirai requests through OpenTelemetry traces. This is a core feature that completes the final pillar in mirai&rsquo;s &lsquo;production first&rsquo; design philosophy.</p>
<p>When tracing is enabled via the otel and otelsdk packages, you can monitor the entire lifecycle of your async computations, from creation through to evaluation, making it easier to debug and optimize performance in production environments. This is especially powerful when used in conjunction with other otel-enabled packages (such as an upcoming Shiny release), providing end-to-end observability across your entire application stack.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/mirai-2-5-0/otel-screenshot.png" alt="Illustrative OpenTelemetry span structure shown in a Jaeger collector UI" />
<figcaption aria-hidden="true"><em>Illustrative OpenTelemetry span structure shown in a Jaeger collector UI</em></figcaption>
</figure>
<h2 id="reproducible-parallel-rng">Reproducible parallel RNG
</h2>
<p>Introduced in mirai 2.4.1: reproducible parallel random number generation. Developed in consultation with our tidymodels colleagues and core members of the mlr team, this is a great example of the R community pulling together to solve a common problem. It addresses a long-standing challenge in parallel computing in R, important for reproducible science.</p>
<p>mirai has, since its early days, used L&rsquo;Ecuyer-CMRG streams for statistically-sound parallel RNG. Streams essentially cut into the RNG&rsquo;s period (a very long sequence of pseudo-random numbers) at intervals that are far apart from each other that they do not in practice overlap. This ensures that statistical results obtained from parallel computations remain correct and valid.</p>
<p>Previously, we only offered the following option, matching the behaviour of base R&rsquo;s parallel package:</p>
<p><strong>Default behaviour</strong> <code>daemons(seed = NULL)</code>: creates independent streams for each daemon. This ensures statistical validity but not numerical reproducibility between runs.</p>
<p>Now, we also offer the following option:</p>
<p><strong>Reproducible mode</strong> <code>daemons(seed = integer)</code>: creates a stream for each <a href="https://mirai.r-lib.org/reference/mirai.html" target="_blank" rel="noopener"><code>mirai()</code></a>
 call rather than each daemon. This guarantees identical results across runs, regardless of the number of daemons used.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># Always provides identical results:</span></span>
<span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/with.html'>with</a></span><span class='o'>(</span></span>
<span>  <span class='nf'><a href='https://mirai.r-lib.org/reference/daemons.html'>daemons</a></span><span class='o'>(</span><span class='m'>3</span>, seed <span class='o'>=</span> <span class='m'>1234L</span><span class='o'>)</span>,</span>
<span>  <span class='nf'><a href='https://mirai.r-lib.org/reference/mirai_map.html'>mirai_map</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>:</span><span class='m'>3</span>, <span class='nv'>rnorm</span>, .args <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span>mean <span class='o'>=</span> <span class='m'>20</span>, sd <span class='o'>=</span> <span class='m'>2</span><span class='o'>)</span><span class='o'>)</span><span class='o'>[</span><span class='o'>]</span></span>
<span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [[1]]</span></span>
<span><span class='c'>#&gt; [1] 19.86409</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; [[2]]</span></span>
<span><span class='c'>#&gt; [1] 19.55834 22.30159</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; [[3]]</span></span>
<span><span class='c'>#&gt; [1] 20.62193 23.06144 19.61896</span></span>
<span></span></code></pre>
</div>
<h2 id="user-interface-improvements">User interface improvements
</h2>
<h3 id="compute-profile-helper-functions">Compute profile helper functions
</h3>
<p><a href="https://mirai.r-lib.org/reference/with_daemons.html" target="_blank" rel="noopener"><code>with_daemons()</code></a>
 and <a href="https://mirai.r-lib.org/reference/with_daemons.html" target="_blank" rel="noopener"><code>local_daemons()</code></a>
 make working with compute profiles much more convenient by allowing the temporary switching of contexts. This means that developers can continue to write mirai code without worrying about the resources on which it is eventually run. End-users now have the ability to change the destination of any mirai computation dynamically using one of these scoped helpers.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Work with specific compute profiles</span>
</span></span><span class="line"><span class="cl"><span class="nf">with_daemons</span><span class="p">(</span><span class="s">&#34;gpu&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">result</span> <span class="o">&lt;-</span> <span class="nf">mirai</span><span class="p">(</span><span class="nf">gpu_intensive_task</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Local version for use inside functions</span>
</span></span><span class="line"><span class="cl"><span class="n">async_gpu_intensive_task</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">local_daemons</span><span class="p">(</span><span class="s">&#34;gpu&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nf">mirai</span><span class="p">(</span><span class="nf">gpu_intensive_task</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="re-designed-daemons">Re-designed <code>daemons()</code>
</h3>
<p>Creating new daemons is now more ergonomic, as it automatically resets existing ones. This provides for more convenient use in contexts such as notebooks, where cells may be run out of order. Manual <code>daemons(0)</code> calls are no longer required to reset daemons.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># Old approach</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">0</span><span class="p">)</span>  <span class="c1"># Had to reset first</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># New approach - automatic reset</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">)</span>  <span class="c1"># Just works, resets if needed</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="new-info-function">New <code>info()</code> function
</h3>
<p>Provides a more succinct alternative to <a href="https://mirai.r-lib.org/reference/status.html" target="_blank" rel="noopener"><code>status()</code></a>
 for reporting key statistics. This is optimized and is now a supported developer interface for programmatic use.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://mirai.r-lib.org/reference/info.html'>info</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; connections  cumulative    awaiting   executing   completed </span></span>
<span><span class='c'>#&gt;           4           4           8           4           2</span></span></code></pre>
</div>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>We extend our gratitude to the R community for their continued feedback and contributions. Special thanks to all contributors who helped shape this release through feature requests, bug reports, and code contributions: <a href="https://github.com/agilly" target="_blank" rel="noopener">@agilly</a>
, <a href="https://github.com/D3SL" target="_blank" rel="noopener">@D3SL</a>
, <a href="https://github.com/DavZim" target="_blank" rel="noopener">@DavZim</a>
, <a href="https://github.com/dipterix" target="_blank" rel="noopener">@dipterix</a>
, <a href="https://github.com/eliocamp" target="_blank" rel="noopener">@eliocamp</a>
, <a href="https://github.com/erydit" target="_blank" rel="noopener">@erydit</a>
, <a href="https://github.com/karangattu" target="_blank" rel="noopener">@karangattu</a>
, <a href="https://github.com/louisaslett" target="_blank" rel="noopener">@louisaslett</a>
, <a href="https://github.com/mikkmart" target="_blank" rel="noopener">@mikkmart</a>
, <a href="https://github.com/sebffischer" target="_blank" rel="noopener">@sebffischer</a>
, <a href="https://github.com/shikokuchuo" target="_blank" rel="noopener">@shikokuchuo</a>
, and <a href="https://github.com/wlandau" target="_blank" rel="noopener">@wlandau</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/mirai-2-5-0/thumbnail-wd.jpg" length="794691" type="image/jpeg" />
    </item>
    <item>
      <title>nanonext 1.7.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/nanonext-1-7-0/</link>
      <pubDate>Tue, 02 Sep 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/nanonext-1-7-0/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<h1 id="introducing-nanonext-breaking-down-language-barriers-in-data-science">Introducing nanonext: breaking down language barriers in data science
</h1>
<p>We&rsquo;re excited to welcome <a href="https://nanonext.r-lib.org" target="_blank" rel="noopener">nanonext</a>
 to the r-lib family! nanonext is R&rsquo;s binding to NNG (Nanomsg Next Generation), a high-performance C messaging library that implements scalability protocols for distributed systems. Because NNG has bindings and ports across multiple languages&mdash;including Python, Go, Rust, C++, and many others&mdash;nanonext enables seamless interoperability between R and other modern programming languages.</p>
<p>The latest version 1.7.0 brings enhanced reliability with improved HTTP client functionality, better handling of custom serialization methods, and more robust event-driven promises.</p>
<p>Get started by installing the package from CRAN now:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;nanonext&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="the-challenge-multi-language-data-science">The challenge: multi-language data science
</h2>
<p>If you&rsquo;ve worked in data science, you&rsquo;ve likely encountered this scenario: your workflow spans multiple programming languages. Perhaps you have Python models, R analysis scripts, Go services, or C++ performance libraries that all need to work together.</p>
<p>Traditionally, making these components communicate means:</p>
<ul>
<li>Writing data to files and reading them back</li>
<li>Building REST APIs and making HTTP calls</li>
<li>Handling different serialization formats like JSON or protocol buffers</li>
<li>Dealing with the latency and complexity that comes with each approach</li>
</ul>
<h2 id="the-solution-nngs-scalability-protocols">The solution: NNG&rsquo;s scalability protocols
</h2>
<p>nanonext changes this by bringing NNG&rsquo;s scalability protocols to R. NNG is a high-performance C library that implements standardized communication patterns like request/reply, publish/subscribe, and pipeline architectures. Any process using NNG can communicate directly with any other NNG process using the same protocol&mdash;regardless of the programming language.</p>
<p>This means that for simple atomic vector types, your R data doesn&rsquo;t even need to be serialized and converted to a common format, enabling direct, real-time communication between processes. For more complex data types, as long as serialization and unserialization methods exist in both languages, then nanonext can be used to send and receive arbitrary binary data.</p>
<h2 id="what-can-you-do-with-nanonext">What can you do with nanonext?
</h2>
<p>nanonext opens up several powerful possibilities for R users:</p>
<p><strong>🔗 Cross-language integration</strong>: Connect R directly with Python machine learning models, Go microservices, Rust compute engines, or C libraries without intermediate files or complex APIs.</p>
<p><strong>⚡ Real-time data pipelines</strong>: Build data systems where different components process data as it flows, perfect for live dashboards or high-frequency analytics.</p>
<p><strong>📡 Modern web integration</strong>: Create WebSocket clients, make asynchronous HTTP requests, or build real-time APIs that integrate with web services.</p>
<p><strong>🚀 Asynchronous programming</strong>: Write non-blocking R code that can handle multiple operations simultaneously, improving performance for concurrent tasks.</p>
<h2 id="python--r-interoperability-example">Python ↔ R interoperability example
</h2>
<p>Here&rsquo;s a concrete example of R and Python working together through NNG&rsquo;s protocols. Both processes use their respective NNG bindings to establish a direct communication channel using NNG&rsquo;s &lsquo;pair&rsquo; protocol&mdash;a simple one-to-one bidirectional communication pattern.</p>
<p><strong>Python side</strong> (using pynng):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pynng</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Create socket to communicate with R</span>
</span></span><span class="line"><span class="cl"><span class="n">socket</span> <span class="o">=</span> <span class="n">pynng</span><span class="o">.</span><span class="n">Pair0</span><span class="p">(</span><span class="n">listen</span><span class="o">=</span><span class="s2">&#34;ipc:///tmp/nanonext.socket&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wait for data from R</span>
</span></span><span class="line"><span class="cl"><span class="n">raw</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">recv</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">array</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">frombuffer</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>  <span class="c1"># [1.1 2.2 3.3 4.4 5.5]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Process data (could be ML model prediction, transformation, etc.)</span>
</span></span><span class="line"><span class="cl"><span class="n">processed</span> <span class="o">=</span> <span class="n">array</span> <span class="o">*</span> <span class="mi">2</span>  <span class="c1"># Simple example: multiply by 2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Send back to R as bytes</span>
</span></span><span class="line"><span class="cl"><span class="n">socket</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">processed</span><span class="o">.</span><span class="n">tobytes</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">socket</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>R side</strong> (using nanonext):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">nanonext</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Connect to Python process</span>
</span></span><span class="line"><span class="cl"><span class="n">sock</span> <span class="o">&lt;-</span> <span class="nf">socket</span><span class="p">(</span><span class="s">&#34;pair&#34;</span><span class="p">,</span> <span class="n">dial</span> <span class="o">=</span> <span class="s">&#34;ipc:///tmp/nanonext.socket&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Send numeric data directly to Python (no serialization needed!)</span>
</span></span><span class="line"><span class="cl"><span class="n">sock</span> <span class="o">|&gt;</span> <span class="nf">send</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="m">1.1</span><span class="p">,</span> <span class="m">2.2</span><span class="p">,</span> <span class="m">3.3</span><span class="p">,</span> <span class="m">4.4</span><span class="p">,</span> <span class="m">5.5</span><span class="p">),</span> <span class="n">mode</span> <span class="o">=</span> <span class="s">&#34;raw&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Receive processed results back from Python</span>
</span></span><span class="line"><span class="cl"><span class="n">processed_data</span> <span class="o">&lt;-</span> <span class="n">sock</span> <span class="o">|&gt;</span> <span class="nf">recv</span><span class="p">(</span><span class="n">mode</span> <span class="o">=</span> <span class="s">&#34;double&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">print</span><span class="p">(</span><span class="n">processed_data</span><span class="p">)</span>  <span class="c1"># [1] 2.2 4.4 6.6 8.8 11.0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Continue with R-specific analysis</span>
</span></span><span class="line"><span class="cl"><span class="nf">summary</span><span class="p">(</span><span class="n">processed_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">plot</span><span class="p">(</span><span class="n">processed_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">close</span><span class="p">(</span><span class="n">sock</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>What just happened?</strong> Your R session sent numerical data directly to Python&rsquo;s memory space, Python processed it (possibly performing inference on a complex ML model), and sent results back&mdash;all happening in memory without touching the disk. This is orders of magnitude faster than traditional file-based approaches.</p>
<h2 id="why-this-matters">Why this matters
</h2>
<p><strong>Speed</strong>: Direct memory communication is dramatically faster than file I/O or HTTP requests. Your R session doesn&rsquo;t wait for disk writes or network round-trips.</p>
<p><strong>Simplicity</strong>: No need to design REST APIs, manage file formats, or handle serialization. Send R vectors directly and receive results back.</p>
<p><strong>Flexibility</strong>: nanonext supports different communication patterns (one-to-one, one-to-many, publish/subscribe) and transport methods (IPC, TCP, WebSocket).</p>
<p><strong>Reliability</strong>: Built on NNG, a mature library that powers mission-critical systems in technology, finance and many other industries.</p>
<p><strong>Asynchronous</strong>: Your R session stays responsive. Send a request for a long-running computation and continue working while it processes in the background.</p>
<h2 id="the-future-is-multi-language">The future is multi-language
</h2>
<p>nanonext facilitates a shift toward more flexible, performance-oriented data science workflows. Instead of being locked into a single language or accepting the overhead of traditional integration methods, you can now build systems where each component uses the best tool for the job.</p>
<p>We&rsquo;re excited to see how the R community uses these capabilities.</p>
<p>Ready to try it? Visit the <a href="https://nanonext.r-lib.org" target="_blank" rel="noopener">package website</a>
 for comprehensive documentation, or explore the code at the <a href="https://github.com/r-lib/nanonext" target="_blank" rel="noopener">GitHub repository</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/nanonext-1-7-0/thumbnail-wd.jpg" length="99873" type="image/jpeg" />
    </item>
    <item>
      <title>Air 0.7.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/air-0-7-0/</link>
      <pubDate>Wed, 11 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/air-0-7-0/</guid>
      <dc:creator>Davis Vaughan</dc:creator>
      <dc:creator>Lionel Henry</dc:creator><description><![CDATA[<p>We&rsquo;re very excited to announce <a href="https://posit-dev.github.io/air/" target="_blank" rel="noopener">Air 0.7.0</a>
, a new release of our extremely fast R formatter. This post will act as a roundup of releases 0.5.0 through 0.7.0, including: even better Positron support, a new feature called autobracing, and an official GitHub Action! If you haven&rsquo;t heard of Air, read our <a href="https://www.tidyverse.org/blog/2025/02/air/" target="_blank" rel="noopener">announcement blog post</a>
 first to get up to speed. To install Air, read our <a href="https://posit-dev.github.io/air/editors.html" target="_blank" rel="noopener">editors guide</a>
.</p>
<h2 id="positron">Positron
</h2>
<p>The <a href="https://open-vsx.org/extension/posit/air-vscode" target="_blank" rel="noopener">Air extension</a>
 is now included in <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>
 by default, and will automatically keep itself up to date. We&rsquo;ve been working hard to ensure that Air leaves a positive first impression, and we think that having Positron come batteries included with Air really helps with that! Positron now also ships with <a href="https://docs.astral.sh/ruff/" target="_blank" rel="noopener">Ruff</a>
, the extremely fast Python formatter and linter, ensuring that you have a great editing experience out of the box, no matter which language you prefer.</p>
<p>We&rsquo;ve also streamlined the process of adding Air to a new or existing project. With dev usethis, you can now run <a href="https://usethis.r-lib.org/dev/reference/use_air.html" target="_blank" rel="noopener"><code>usethis::use_air()</code></a>
 to automatically configure recommended Air settings. In particular, this will:</p>
<ul>
<li>
<p>Create an <a href="https://posit-dev.github.io/air/configuration.html#configuration-recommendations" target="_blank" rel="noopener">empty <code>air.toml</code></a>
.</p>
</li>
<li>
<p>Create <code>.vscode/settings.json</code> filled with the following settings. This enables <code>Format on Save</code> within your workspace.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;[r]&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;editor.formatOnSave&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;editor.defaultFormatter&#34;</span><span class="p">:</span> <span class="s2">&#34;Posit.air-vscode&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Create <code>.vscode/extensions.json</code> filled with the following settings. This automatically prompts contributors that don&rsquo;t have the Air extension to install it when they open your workspace, ensuring that everyone is using the same formatter!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;recommendations&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Posit.air-vscode&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Update your <code>.Rbuildignore</code> to exclude Air related configuration, if you&rsquo;re working on an R package.</p>
</li>
</ul>
<p>Once you&rsquo;ve used usethis to configure Air, you can now immediately reformat your entire workspace by running <code>Air: Format Workspace Folder</code> from the Command Palette (accessible via <code>Cmd + Shift + P</code> on Mac/Linux, or <code>Ctrl + Shift + P</code> on Windows). I&rsquo;ve found that this is invaluable for adopting Air in an existing project!</p>
<p>To summarize, we&rsquo;ve reduced our advice on adding Air to an existing project down to:</p>
<ul>
<li>
<p>Open Positron</p>
</li>
<li>
<p>Run <a href="https://usethis.r-lib.org/reference/use_air.html" target="_blank" rel="noopener"><code>usethis::use_air()</code></a>
</p>
</li>
<li>
<p>Run <code>Air: Format Workspace Folder</code></p>
</li>
<li>
<p>Commit, push, and then enjoy using <code>Format on Save</code> forevermore 😄</p>
</li>
</ul>
<h2 id="more-editors">More editors!
</h2>
<p>Positron isn&rsquo;t the only editor that&rsquo;s received some love! We now have official documentation for using Air in the following editors:</p>
<ul>
<li>
<p><a href="https://posit-dev.github.io/air/editor-zed.html" target="_blank" rel="noopener">Zed</a>
</p>
</li>
<li>
<p><a href="https://posit-dev.github.io/air/editor-neovim.html" target="_blank" rel="noopener">Neovim</a>
</p>
</li>
<li>
<p><a href="https://posit-dev.github.io/air/editor-helix.html" target="_blank" rel="noopener">Helix</a>
</p>
</li>
</ul>
<p>We&rsquo;re very proud of the fact that Air can be used within any editor, not just RStudio and Positron! This documentation was a community effort - thanks in particular to <a href="https://github.com/taplasz" target="_blank" rel="noopener">@taplasz</a>
, <a href="https://github.com/PMassicotte" target="_blank" rel="noopener">@PMassicotte</a>
, <a href="https://github.com/m-muecke" target="_blank" rel="noopener">@m-muecke</a>
, <a href="https://github.com/TymekDev" target="_blank" rel="noopener">@TymekDev</a>
, and <a href="https://github.com/wurli" target="_blank" rel="noopener">@wurli</a>
.</p>
<h2 id="autobracing">Autobracing
</h2>
<p>Autobracing is the process of adding braces (i.e. <a href="https://rdrr.io/r/base/Paren.html" target="_blank" rel="noopener"><code>{ }</code></a>
) to if statements, loops, and function definitions to create more consistent, readable, and portable code. It looks like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="kr">for</span> <span class="p">(</span><span class="n">i</span> <span class="kr">in</span> <span class="nf">seq_along</span><span class="p">(</span><span class="n">x</span><span class="p">))</span> <span class="n">x[[i]]</span> <span class="o">&lt;-</span> <span class="n">x[[i]]</span> <span class="o">+</span> <span class="m">1L</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Becomes:</span>
</span></span><span class="line"><span class="cl"><span class="kr">for</span> <span class="p">(</span><span class="n">i</span> <span class="kr">in</span> <span class="nf">seq_along</span><span class="p">(</span><span class="n">x</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">x[[i]]</span> <span class="o">&lt;-</span> <span class="n">x[[i]]</span> <span class="o">+</span> <span class="m">1L</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nf">call_that_spans_lines</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">y</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fixed_option</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Becomes:</span>
</span></span><span class="line"><span class="cl"><span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">call_that_spans_lines</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">y</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fixed_option</span> <span class="o">=</span> <span class="kc">FALSE</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It&rsquo;s particularly important to autobrace multiline if statements for <em>portability</em>, which we roughly define as the ability to copy and paste that if statement into any context and have it still parse correctly. Consider the following if statement:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">do_something</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">this</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="n">this</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nf">do_this</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="kr">else</span> 
</span></span><span class="line"><span class="cl">    <span class="nf">do_that</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As written, this is correct R code, but if you were to pull out the if statement and place it in a file at &ldquo;top level&rdquo; and try to run it, you&rsquo;d see a parse error:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="kr">if</span> <span class="p">(</span><span class="n">this</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="nf">do_this</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">else</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">do_that</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error: unexpected &#39;else&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In practice, this typically bites you when you&rsquo;re debugging and you send a chunk of lines to the console:</p>
<video controls autoplay loop muted width="100%" src="https://posit-open-source.netlify.app/blog/tidyverse/2025/air-0-7-0/video/portable-if-statement.mov" style="border: 2px solid #CCC;">
</video>
<p>Air autobraces this if statement to the following, which has no issues with portability:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">do_something</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">this</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">if</span> <span class="p">(</span><span class="n">this</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">do_this</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="kr">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">do_that</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="give-side-effects-some-air">Give side effects some Air
</h3>
<p>We believe code that create <em>side effects</em> which modify state or affect control flow are important enough to live on their own line. For example, the following <a href="https://rdrr.io/r/base/stop.html" target="_blank" rel="noopener"><code>stop()</code></a>
 call is an example of a side effect, so it moves to its own line and is autobraced:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="kr">if</span> <span class="p">(</span><span class="nf">anyNA</span><span class="p">(</span><span class="n">x</span><span class="p">))</span> <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;`x` can&#39;t contain missing values.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Becomes:</span>
</span></span><span class="line"><span class="cl"><span class="kr">if</span> <span class="p">(</span><span class="nf">anyNA</span><span class="p">(</span><span class="n">x</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;`x` can&#39;t contain missing values.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You might be thinking, &ldquo;But I like my single line if statements!&rdquo; We do too! Air still allows single line if statements if they look to be used for their <em>value</em> rather than for their <em>side effect</em>. These single line if statements are still allowed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">x</span> <span class="o">&lt;-</span> <span class="kr">if</span> <span class="p">(</span><span class="n">condition</span><span class="p">)</span> <span class="n">this</span> <span class="kr">else</span> <span class="n">that</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">x</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">%||%</span> <span class="kr">if</span> <span class="p">(</span><span class="n">condition</span><span class="p">)</span> <span class="n">this</span> <span class="kr">else</span> <span class="n">that</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">list</span><span class="p">(</span><span class="n">a</span> <span class="o">=</span> <span class="kr">if</span> <span class="p">(</span><span class="n">condition</span><span class="p">)</span> <span class="n">this</span> <span class="kr">else</span> <span class="n">that</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Similarly, single line function definitions are also still allowed if they don&rsquo;t already have braces and don&rsquo;t exceed the line length:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">add_one</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="n">x</span> <span class="o">+</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bools</span> <span class="o">&lt;-</span> <span class="nf">map_lgl</span><span class="p">(</span><span class="n">xs</span><span class="p">,</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="nf">is.logical</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nf">length</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">==</span> <span class="m">1L</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nf">is.na</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For the full set of rules, check out our <a href="https://posit-dev.github.io/air/formatter.html#autobracing" target="_blank" rel="noopener">documentation on autobracing</a>
.</p>
<h2 id="empty-braces">Empty braces
</h2>
<p>You may have noticed the following forced expansion of empty <a href="https://rdrr.io/r/base/Paren.html" target="_blank" rel="noopener"><code>{}</code></a>
 in previous versions of Air:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">dummy</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">()</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Previously became:</span>
</span></span><span class="line"><span class="cl"><span class="n">dummy</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">tryCatch</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="n">error</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Previously became:</span>
</span></span><span class="line"><span class="cl"><span class="nf">tryCatch</span><span class="p">(</span><span class="n">fn</span><span class="p">,</span> <span class="n">error</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">my_fn</span><span class="p">(</span><span class="n">expr</span> <span class="o">=</span> <span class="p">{},</span> <span class="n">option</span> <span class="o">=</span> <span class="kc">TRUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Previously became:</span>
</span></span><span class="line"><span class="cl"><span class="nf">my_fn</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">expr</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> 
</span></span><span class="line"><span class="cl">  <span class="n">option</span> <span class="o">=</span> <span class="kc">TRUE</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As of 0.7.0, empty braces <a href="https://rdrr.io/r/base/Paren.html" target="_blank" rel="noopener"><code>{}</code></a>
 are now never expanded, which retains the original form of each of these examples.</p>
<h2 id="skip-configuration"><code>skip</code> configuration
</h2>
<p>In <a href="https://www.tidyverse.org/blog/2025/02/air/#how-can-i-disable-formatting" target="_blank" rel="noopener">our release post</a>
, we detailed how to disable formatting using a <code># fmt: skip</code> comment for a single expression, or a <code># fmt: skip file</code> comment for an entire file. Skip comments are useful for disabling formatting for one-off function calls, but sometimes you may find yourself repeatedly using functions from a domain specific language (DSL) that doesn&rsquo;t follow conventional formatting rules. For example, the igraph package contains a DSL for constructing a graph from a literal representation:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">igraph</span><span class="o">::</span><span class="nf">graph_from_literal</span><span class="p">(</span><span class="n">A</span> <span class="o">+-+</span> <span class="n">B</span> <span class="o">+---+</span> <span class="n">C</span> <span class="o">++</span> <span class="n">D</span> <span class="o">+</span> <span class="n">E</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>By default, Air would format this as:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">igraph</span><span class="o">::</span><span class="nf">graph_from_literal</span><span class="p">(</span><span class="n">A</span> <span class="o">+</span> <span class="o">-+</span><span class="n">B</span> <span class="o">+</span> <span class="o">---+</span><span class="n">C</span> <span class="o">+</span> <span class="o">+</span><span class="n">D</span> <span class="o">+</span> <span class="n">E</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If you use <code>graph_from_literal()</code> often, it would be annoying to add <code># fmt: skip</code> comments at every call site. Instead, <code>air.toml</code> now supports a <code>skip</code> field that allows you to specify function names that you never want formatting for. Specifying this would retain the original formatting of the <code>graph_from_literal()</code> call, even without a <code># fmt: skip</code> comment:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">skip</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;graph_from_literal&#34;</span><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the short term, you may also want to use this for <a href="https://tibble.tidyverse.org/reference/tribble.html" target="_blank" rel="noopener"><code>tibble::tribble()</code></a>
 calls, i.e. <code>skip = [&quot;tribble&quot;]</code>. In the long term, we&rsquo;re hoping to provide more sophisticated tooling for formatting using a <a href="https://github.com/posit-dev/air/issues/113" target="_blank" rel="noopener">specified alignment</a>
.</p>
<h2 id="github-action">GitHub Action
</h2>
<p>Air now has an official GitHub Action, <a href="https://github.com/posit-dev/setup-air" target="_blank" rel="noopener"><code>setup-air</code></a>
. This action really only has one job - to get Air installed on your GitHub runner and put on the <code>PATH</code>. The basic usage is:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install Air</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">posit-dev/setup-air@v1</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>If you need to pin a version:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install Air 0.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">posit-dev/setup-air@v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0.4.4&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>From there, you can call Air&rsquo;s CLI in downstream steps. A minimal workflow that errors if any files require formatting might look like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Install Air</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">posit-dev/setup-air@v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Check formatting</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="l">air format . --check</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Rather than creating the workflow file yourself, we instead recommend using usethis to pull in our <a href="https://github.com/posit-dev/setup-air/blob/main/examples/format-suggest.yaml" target="_blank" rel="noopener">example workflow</a>
:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">usethis</span><span class="o">::</span><span class="nf">use_github_action</span><span class="p">(</span><span class="n">url</span> <span class="o">=</span> <span class="s">&#34;https://github.com/posit-dev/setup-air/blob/main/examples/format-suggest.yaml&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is a special workflow that runs on pull requests. It calls <code>air format</code> and then uses <a href="https://github.com/reviewdog/action-suggester" target="_blank" rel="noopener"><code>reviewdog/action-suggester</code></a>
 to push any formatting diffs as GitHub Suggestion comments on your pull request. It looks like this:</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://posit-open-source.netlify.app/blog/tidyverse/2025/air-0-7-0/figs/format-suggest-example.png"
      alt="" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>You can accept all suggestions in a single batch, which will then rerun the format check, along with any other GitHub workflows (like an R package check), so you can feel confident that accepting the changes hasn&rsquo;t broken anything.</p>
<p>We like this workflow because it provides an easy way for external contributors who aren&rsquo;t using Air to still abide by your formatting rules. The external contributor can even accept the suggestions themselves, so by the time you look at their pull request it&rsquo;s already good to go from a formatting perspective ✅!</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thanks to the 49 users who helped make this release possible by finding bugs, discussing issues, contributing documentation, and writing code: <a href="https://github.com/adisarid" target="_blank" rel="noopener">@adisarid</a>
, <a href="https://github.com/aronatkins" target="_blank" rel="noopener">@aronatkins</a>
, <a href="https://github.com/ateucher" target="_blank" rel="noopener">@ateucher</a>
, <a href="https://github.com/avhz" target="_blank" rel="noopener">@avhz</a>
, <a href="https://github.com/aymennasri" target="_blank" rel="noopener">@aymennasri</a>
, <a href="https://github.com/christophe-gouel" target="_blank" rel="noopener">@christophe-gouel</a>
, <a href="https://github.com/dkStevensNZed" target="_blank" rel="noopener">@dkStevensNZed</a>
, <a href="https://github.com/eitsupi" target="_blank" rel="noopener">@eitsupi</a>
, <a href="https://github.com/ELICHOS" target="_blank" rel="noopener">@ELICHOS</a>
, <a href="https://github.com/fh-mthomson" target="_blank" rel="noopener">@fh-mthomson</a>
, <a href="https://github.com/fzenoni" target="_blank" rel="noopener">@fzenoni</a>
, <a href="https://github.com/gaborcsardi" target="_blank" rel="noopener">@gaborcsardi</a>
, <a href="https://github.com/grasshoppermouse" target="_blank" rel="noopener">@grasshoppermouse</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/idavydov" target="_blank" rel="noopener">@idavydov</a>
, <a href="https://github.com/j-dobner" target="_blank" rel="noopener">@j-dobner</a>
, <a href="https://github.com/jacpete" target="_blank" rel="noopener">@jacpete</a>
, <a href="https://github.com/jeffkeller-einc" target="_blank" rel="noopener">@jeffkeller-einc</a>
, <a href="https://github.com/jhk0530" target="_blank" rel="noopener">@jhk0530</a>
, <a href="https://github.com/joakimlinde" target="_blank" rel="noopener">@joakimlinde</a>
, <a href="https://github.com/JosephBARBIERDARNAL" target="_blank" rel="noopener">@JosephBARBIERDARNAL</a>
, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>
, <a href="https://github.com/kkanden" target="_blank" rel="noopener">@kkanden</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/Kupac" target="_blank" rel="noopener">@Kupac</a>
, <a href="https://github.com/kv9898" target="_blank" rel="noopener">@kv9898</a>
, <a href="https://github.com/lcolladotor" target="_blank" rel="noopener">@lcolladotor</a>
, <a href="https://github.com/lulunac27a" target="_blank" rel="noopener">@lulunac27a</a>
, <a href="https://github.com/m-muecke" target="_blank" rel="noopener">@m-muecke</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/matanhakim" target="_blank" rel="noopener">@matanhakim</a>
, <a href="https://github.com/njtierney" target="_blank" rel="noopener">@njtierney</a>
, <a href="https://github.com/novica" target="_blank" rel="noopener">@novica</a>
, <a href="https://github.com/ntluong95" target="_blank" rel="noopener">@ntluong95</a>
, <a href="https://github.com/philibe" target="_blank" rel="noopener">@philibe</a>
, <a href="https://github.com/PMassicotte" target="_blank" rel="noopener">@PMassicotte</a>
, <a href="https://github.com/RobinKohrs" target="_blank" rel="noopener">@RobinKohrs</a>
, <a href="https://github.com/salim-b" target="_blank" rel="noopener">@salim-b</a>
, <a href="https://github.com/sawelch-NIVA" target="_blank" rel="noopener">@sawelch-NIVA</a>
, <a href="https://github.com/schochastics" target="_blank" rel="noopener">@schochastics</a>
, <a href="https://github.com/Sebastian-T-T" target="_blank" rel="noopener">@Sebastian-T-T</a>
, <a href="https://github.com/stevenpav-helm" target="_blank" rel="noopener">@stevenpav-helm</a>
, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>
, <a href="https://github.com/taplasz" target="_blank" rel="noopener">@taplasz</a>
, <a href="https://github.com/tbadams45cdm" target="_blank" rel="noopener">@tbadams45cdm</a>
, <a href="https://github.com/wurli" target="_blank" rel="noopener">@wurli</a>
, <a href="https://github.com/xx02al" target="_blank" rel="noopener">@xx02al</a>
, <a href="https://github.com/Yunuuuu" target="_blank" rel="noopener">@Yunuuuu</a>
, and <a href="https://github.com/yutannihilation" target="_blank" rel="noopener">@yutannihilation</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/air-0-7-0/thumbnail-wd.jpg" length="182665" type="image/jpeg" />
    </item>
    <item>
      <title>Fonts in R</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/</link>
      <pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/</guid>
      <dc:creator>Thomas Lin Pedersen</dc:creator><description><![CDATA[<style type='text/css'>
pre {
  text-wrap: nowrap;
  overflow-x: scroll;
}
figure {
  margin-top: 2em;
}
figcaption {
  text-align: center;
  margin-top: 1em;
}
table {
  max-width: 99%
}
</style>
<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>(An updated version of this blog post will be available at <a href="https://systemfonts.r-lib.org" target="_blank" rel="noopener">the systemfonts webpage</a>
)</p>
<p>The purpose of this document is to give you a thorough overview of fonts in R. However, for this to be possible, you&rsquo;ll first need a basic understanding of fonts in general. If you already have a thorough understanding of digital typography you can skip to <a href="#font-handling-in-r">the next section</a>
.</p>
<h2 id="digital-typography">Digital typography
</h2>
<p>Many books could be, and have been, written about the subject of typography. This blog post is not meant to be an exhaustive deep dive into all areas of this vast subject. Rather, it is meant to give you just enough understanding of core concepts and terminology to appreciate how it all plays into using fonts in R.</p>
<h3 id="typeface-or-font">Typeface or font?
</h3>
<p>There is a good chance that you, like 99% of world, use &ldquo;font&rdquo; as the term describing &ldquo;the look&rdquo; of the letters you type. You may, perhaps, have heard the term &ldquo;typeface&rdquo; as well and thought it synonymous. This is in fact slightly wrong, and a great deal of typography snobbery has been dealt out on that account (much like the distinction between packages and libraries in R). It is a rather inconsequential mix-up for the most part, especially because 99% of the population wouldn&rsquo;t bat an eye if you use them interchangeably. However, the distinction between the two serves as a good starting point to talk about other terms in digital typography as well as the nature of font files, so let&rsquo;s dive in.</p>
<p>When most people use the word &ldquo;font&rdquo; or &ldquo;font family&rdquo;, what they are actually describing is a typeface. A <strong>typeface</strong> is a style of lettering that forms a cohesive whole. As an example, consider the well-known &ldquo;Helvetica&rdquo; typeface. This name embraces many different weights (bold, normal, light) as well as slanted (italic) and upright. However, all of these variations are all as much Helvetica as the others - they are all part of the same typeface.</p>
<p>A <strong>font</strong> is a subset of a typeface, describing a particular variation of the typeface, i.e. the combination of weight, width, and slant that comes together to describe the specific subset of a typeface that is used. We typically give a specific combination of these features a name, like &ldquo;bold&rdquo; or &ldquo;medium&rdquo; or &ldquo;italic&rdquo;, which we call the <strong>font style</strong><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. In other words, a font is a particularly style within a typeface.</p>
<div class="highlight">
<div class="figure" style="text-align: center">
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-2-1.png" alt="Different fonts from the Avenir Next typeface" width="700px" />
<p class="caption">
Different fonts from the Avenir Next typeface
</p>
</div>
</div>
<p>In the rest of this document we will use the terms typeface and font with the meaning described above.</p>
<h3 id="font-files">Font files
</h3>
<p>Next, we need to talk about how typefaces are represented for use by computers. Font files record information on how to draw the individual glyphs (characters), but also instructions about how to draw sequences of glyphs like distance adjustments (kerning) and substitution rules (ligatures). Font files typically encode a single font but can encode a full typeface:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>typefaces</span> <span class='o'>&lt;-</span> <span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/system_fonts.html'>system_fonts</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>[</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"path"</span>, <span class='s'>"index"</span>, <span class='s'>"family"</span>, <span class='s'>"style"</span><span class='o'>)</span><span class='o'>]</span></span>
<span></span>
<span><span class='c'># Full typeface in one file</span></span>
<span><span class='nv'>typefaces</span><span class='o'>[</span><span class='nv'>typefaces</span><span class='o'>$</span><span class='nv'>family</span> <span class='o'>==</span> <span class='s'>"Helvetica"</span>, <span class='o'>]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 6 × 4</span></span></span>
<span><span class='c'>#&gt;   path                                index family    style        </span></span>
<span><span class='c'>#&gt;   <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>                               <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>     <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>        </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>1</span> /System/Library/Fonts/Helvetica.ttc     2 Helvetica Oblique      </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>2</span> /System/Library/Fonts/Helvetica.ttc     4 Helvetica Light        </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>3</span> /System/Library/Fonts/Helvetica.ttc     5 Helvetica Light Oblique</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>4</span> /System/Library/Fonts/Helvetica.ttc     1 Helvetica Bold         </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>5</span> /System/Library/Fonts/Helvetica.ttc     3 Helvetica Bold Oblique </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>6</span> /System/Library/Fonts/Helvetica.ttc     0 Helvetica Regular</span></span>
<span></span><span></span>
<span><span class='c'># One font per font file</span></span>
<span><span class='nv'>typefaces</span><span class='o'>[</span><span class='nv'>typefaces</span><span class='o'>$</span><span class='nv'>family</span> <span class='o'>==</span> <span class='s'>"Arial"</span>, <span class='o'>]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 4 × 4</span></span></span>
<span><span class='c'>#&gt;   path                                                     index family style      </span></span>
<span><span class='c'>#&gt;   <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>                                                    <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>      </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>1</span> /System/Library/Fonts/Supplemental/Arial.ttf                 0 Arial  Regular    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>2</span> /System/Library/Fonts/Supplemental/Arial Bold.ttf            0 Arial  Bold       </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>3</span> /System/Library/Fonts/Supplemental/Arial Bold Italic.ttf     0 Arial  Bold Italic</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>4</span> /System/Library/Fonts/Supplemental/Arial Italic.ttf          0 Arial  Italic</span></span>
<span></span></code></pre>
</div>
<p>Here, each row is a font, with <strong>family</strong> giving the name of the typeface, and <strong>style</strong> the font style.</p>
<p>It took a considerable number of tries before the world managed to nail the digitial representation of fonts, leading to a proliferation of file types. As an R user, there are three formats that are particularly improtant:</p>
<ul>
<li>
<p><strong>TrueType</strong> (ttf/ttc). Truetype is the baseline format that all modern formats stand on top of. It was developed by Apple in the &rsquo;80s and became popular due to its great balance between size and quality. Fonts can be encoded, either as scalable paths, or as bitmaps of various sizes, the former generally being preferred as it allows for seamless scaling and small file size at the same time.</p>
</li>
<li>
<p><strong>OpenType</strong> (otf/otc). OpenType was created by Microsoft and Adobe to improve upon TrueType. While TrueType was a great success, the number of glyphs it could contain was limited and so was its support for selecting different features during <a href="#text-shaping">shaping</a>
. OpenType resolved these issues, so if you want access to advanced typography features you&rsquo;ll need a font in OpenType format.</p>
</li>
<li>
<p><strong>Web Open Font Format</strong> (woff/woff2). TrueType and OpenType tend to create large files. Since a large percentage of the text consumed today is delivered over the internet this creates a problem. WOFF resolves this problem by acting as a compression wrapper around TrueType/OpenType to reduce file sizes while also limiting the number of advanced features provided to those relevant to web fonts. The woff2 format is basically identical to woff except it uses the more efficient <a href="https://en.wikipedia.org/wiki/Brotli" target="_blank" rel="noopener">brotli</a>
 compression algorithm. WOFF was designed specifically to be delivered over the internet and support is still a bit limited outside of browsers.</p>
</li>
</ul>
<p>While we have mainly talked about font files as containers for the shape of glyphs, they also carries a lot of other information needed for rendering text in a way pleasant for reading. Font level information records a lot of stylistic information about typeface/font, statistics on the number of glyphs and how many different mappings between character encodings and glyphs it contains, and overall sizing information such as the maximum descend of the font, the position of an underline relative to the baseline etc. systemfonts provdies a convenient way to access this data from R:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>dplyr</span><span class='nf'>::</span><span class='nf'><a href='https://pillar.r-lib.org/reference/glimpse.html'>glimpse</a></span><span class='o'>(</span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/font_info.html'>font_info</a></span><span class='o'>(</span>family <span class='o'>=</span> <span class='s'>"Helvetica"</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Rows: 1</span></span>
<span><span class='c'>#&gt; Columns: 24</span></span>
<span><span class='c'>#&gt; $ path               <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span> "/System/Library/Fonts/Helvetica.ttc"</span></span>
<span><span class='c'>#&gt; $ index              <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> 0</span></span>
<span><span class='c'>#&gt; $ family             <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span> "Helvetica"</span></span>
<span><span class='c'>#&gt; $ style              <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span> "Regular"</span></span>
<span><span class='c'>#&gt; $ italic             <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> FALSE</span></span>
<span><span class='c'>#&gt; $ bold               <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> FALSE</span></span>
<span><span class='c'>#&gt; $ monospace          <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> FALSE</span></span>
<span><span class='c'>#&gt; $ weight             <span style='color: #555555; font-style: italic;'>&lt;ord&gt;</span> normal</span></span>
<span><span class='c'>#&gt; $ width              <span style='color: #555555; font-style: italic;'>&lt;ord&gt;</span> normal</span></span>
<span><span class='c'>#&gt; $ kerning            <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> FALSE</span></span>
<span><span class='c'>#&gt; $ color              <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> FALSE</span></span>
<span><span class='c'>#&gt; $ scalable           <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> TRUE</span></span>
<span><span class='c'>#&gt; $ vertical           <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span> FALSE</span></span>
<span><span class='c'>#&gt; $ n_glyphs           <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> 2252</span></span>
<span><span class='c'>#&gt; $ n_sizes            <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> 0</span></span>
<span><span class='c'>#&gt; $ n_charmaps         <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> 10</span></span>
<span><span class='c'>#&gt; $ bbox               <span style='color: #555555; font-style: italic;'>&lt;list&gt;</span> &lt;-11.406250, 17.343750, -5.765625, 13.453125&gt;</span></span>
<span><span class='c'>#&gt; $ max_ascend         <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> 9.234375</span></span>
<span><span class='c'>#&gt; $ max_descend        <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> -2.765625</span></span>
<span><span class='c'>#&gt; $ max_advance_width  <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> 18</span></span>
<span><span class='c'>#&gt; $ max_advance_height <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> 12</span></span>
<span><span class='c'>#&gt; $ lineheight         <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> 12</span></span>
<span><span class='c'>#&gt; $ underline_pos      <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> -1.203125</span></span>
<span><span class='c'>#&gt; $ underline_size     <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> 0.59375</span></span>
<span></span></code></pre>
</div>
<p>Further, for each glyph there is a range of information in addition to its shape:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/glyph_info.html'>glyph_info</a></span><span class='o'>(</span><span class='s'>"j"</span>, family <span class='o'>=</span> <span class='s'>"Helvetica"</span>, size <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 1 × 9</span></span></span>
<span><span class='c'>#&gt;   glyph index width height x_bearing y_bearing x_advance y_advance bbox     </span></span>
<span><span class='c'>#&gt;   <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>     <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>     <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>     <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>     <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;list&gt;</span>   </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>1</span> j        77     6     27        -<span style='color: #BB0000;'>1</span>        21         7         0 <span style='color: #555555;'>&lt;dbl [4]&gt;</span></span></span>
<span></span></code></pre>
</div>
<p>These terms are more easily understood with a diagram:</p>
<div class="highlight">
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-6-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<p>The <code>x_advance</code> in particular is important when rendering text because it tells you how far to move to the right before rendering the next glyph (ignoring for a bit the concept of kerning)</p>
<h3 id="text-shaping">Text shaping
</h3>
<p>The next important concept to understand is <strong>text shaping</strong>, which, in the simplest of terms, is to convert a succession of characters into a sequence of glyphs along with their locations. Important here is the distinction between <strong>characters</strong>, the things you think of as letters, and <strong>glyphs</strong>, which is what the font will draw. For example, think of the character &ldquo;f&rdquo;, which is often tricky to draw because the &ldquo;hook&rdquo; of the f can interfere with other characters. To solve this problem, many typefaces include <strong>ligatures</strong>, like &ldquo;ﬁ&rdquo;, which are used for specific pairs of characaters. Ligatures are extremely important for languages like Arabic.</p>
<p>A few of the challenges of text shaping include kerning, bidirectional text, and font substitution. <strong>Kerning</strong> is the adjustment of distance between specific pairs of characters. For example, you can put &ldquo;VM&rdquo; a little closer together but &ldquo;OO&rdquo; needs to be a little further apart. Kerning is an integral part of all modern text rendering and you will almost solemnly notice it when it is absent (or worse, <a href="https://www.fastcompany.com/91324550/kerning-on-pope-francis-tomb-is-a-travesty" target="_blank" rel="noopener">wrongly applied</a>
).</p>
<p>Not every language writes text in the same direction, but regardless of your native script, you are likely to use arabic numerals which are always written left-to-right. This gives rise to the challenge of <strong>bidirectional</strong> (or bidi) text, which mixes text flowing in different directions. This imposes a whole new range of challenges!</p>
<p>Finally, you might request a character that a font doesn&rsquo;t contain. One way to deal with this is to render a glyph representing a missing glyph, usually an empty box or a question mark. But it&rsquo;s typically more useful to use the correct glyph from a different font. This is called <strong>font fallback</strong> and happens all the time for emojis, but can also happen when you suddenly change script without bothering to pick a new font. Font fallback is an imprecise science, typically relying on an operating system font that has a very large number of characters, but might look very different from your existing font.</p>
<p>Once you have determined the order and location of glyphs, you are still not done. Text often needs to be wrapped to fit into a specific width, it may need a specific justification, perhaps, indentation or tracking must be applied, etc. Thankfully, all of this is generally a matter of (often gnarly) math that you just have to get right. That is, all except text wrapping which should happen at the right boundaries, and may need to break up a word and inserting a hyphen etc.</p>
<p>Like I said, the pit of despair is bottomless&hellip;</p>
<h2 id="font-handling-in-r">Font handling in R
</h2>
<p>You hopefully arrive at this section with an appreciation of the horrors that goes into rendering text. If not, maybe this <a href="https://faultlore.com/blah/text-hates-you/" target="_blank" rel="noopener">blog post</a>
 will convince you.</p>
<p>Are you still here? Good.</p>
<p>Now that you understand the basics of what goes into handling fonts and text, we can now discuss the details of fonts in R specifically.</p>
<h3 id="fonts-and-text-from-a-user-perspective">Fonts and text from a user perspective
</h3>
<p>The users perception of working with fonts in R is largely shaped by plots. This means using either base or grid graphics or one of the packages that have been build on top of it, like <a href="https://ggplot2.tidyverse.org" target="_blank" rel="noopener">ggplot2</a>
. While the choice of tool will affect <em>where</em> you specify the font to use, they generally agree on how to specify it.</p>
<table style="width:99%;">
<colgroup>
<col style="width: 37%" />
<col style="width: 5%" />
<col style="width: 18%" />
<col style="width: 37%" />
</colgroup>
<thead>
<tr>
<th>Graphic system</th>
<th>Argument</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td><em>Typeface</em></td>
<td><em>Font</em></td>
<td><em>Size</em></td>
</tr>
<tr>
<td><p><strong>Base</strong></p>
<p><em>Arguments are passed to <code>par()</code> to set globally or directly to the call that renders text (e.g. <code>text()</code>)</em></p></td>
<td><code>family</code></td>
<td><code>font</code></td>
<td><code>cra</code> (pixels) or <code>cin</code> (inches) multiplied by <code>cex</code></td>
</tr>
<tr>
<td><p><strong>Grid</strong></p>
<p>Arguments are passed to the <code>gp</code> argument of relevant grobs using the <code>gpar()</code> constructor</p></td>
<td><code>fontfamily</code></td>
<td><code>fontface</code></td>
<td><code>fontsize</code> (points) multiplied by <code>cex</code></td>
</tr>
<tr>
<td><p><strong>ggplot2</strong></p>
<p>Arguments are set in <code>element_text()</code> to alter theme fonts or directly in the geom call to alter geom fonts</p></td>
<td><code>family</code></td>
<td><code>face</code> (in <code>element_text()</code>) or <code>fontface</code> (in geoms)</td>
<td><code>size</code> (points when used in <code>element_text()</code>, depends on the value of <code>size.unit</code> argument when used in geom)</td>
</tr>
</tbody>
</table>
<p>From the table it is clear that in R <code>fontfamily</code>/<code>family</code> is used to describe the typeface and <code>font</code>/<code>fontface</code>/<code>face</code> is used to select a font from the typeface. Size settings is just a plain mess.</p>
<p>The major limitation in <code>fontface</code> (and friends) is that it takes a number, not a string, and you can only select from four options: <code>1</code>: plain, <code>2</code>: bold, <code>3</code>: italic, and <code>4</code>: bold-italic. This means, for example, that there&rsquo;s no way to select Futura Condensed Extra Bold. Another limitation is that it&rsquo;s not possible to specify any font variations such as using tabular numbers or stylistic ligatures.</p>
<h3 id="fonts-and-text-from-a-graphics-device-perspective">Fonts and text from a graphics device perspective
</h3>
<p>In R, a graphics device is the part responsible for doing the rendering you request and put it on your screen or in a file. When you call <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>png()</code></a>
 or <a href="https://ragg.r-lib.org/reference/agg_png.html" target="_blank" rel="noopener"><code>ragg::agg_png()</code></a>
 you open up a graphics device that will receive all the plotting instructions from R. Both graphics devices will ultimately produce the same file type (PNG), but how they choose to handle and respond to the plotting instructions may differ (greatly). Nowhere is this difference more true than when it comes to text rendering.</p>
<p>After a user has made a call that renders some text, it is funneled through the graphic system (base or grid), handed off to the graphics engine, which ultimately asks the graphics device to render the text. From the perspective of the graphics device it is much the same information that the user provided which are presented to it. The <a href="https://rdrr.io/r/graphics/text.html" target="_blank" rel="noopener"><code>text()</code></a>
 method of the device are given an array of characters, the typeface, the size in points, and an integer denoting if the style is regular, bold, italic, or bold-italic.</p>
<figure>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/text_call_flow.svg" data-fig-alt="A diagram showing the flow of text rendering instructions from ggplot2, grid, the graphics engine, and down to the graphics device. Very little changes in the available information about the font during the flow" alt="Flow of font information through the R rendering stack" />
<figcaption aria-hidden="true">Flow of font information through the R rendering stack</figcaption>
</figure>
<p>This means that it is up to the graphics device to find the approprate font file (using the provided typeface and font style) and shape the text with all that that entails. This is a lot of work, which is why text is handled so inconsistently between graphics devices. Issues can range from not being able to find fonts installed on the computer, to not providing font fallback mechanisms, or even handling right-to-left text. It may also be that certain font file formats are not well supported so that e.g. color emojis are not rendered correctly.</p>
<p>There have been a number of efforts to resolve these problems over the years:</p>
<ul>
<li>
<p><strong>extrafont</strong>: Developed by Winston Chang, <a href="https://github.com/wch/extrafont" target="_blank" rel="noopener">extrafont</a>
 sought to mainly improve the situation for the <a href="https://rdrr.io/r/grDevices/pdf.html" target="_blank" rel="noopener"><code>pdf()</code></a>
 device which generally only had access to the postscript fonts that comes with R. The package allows the <a href="https://rdrr.io/r/grDevices/pdf.html" target="_blank" rel="noopener"><code>pdf()</code></a>
 device to get access to TrueType fonts installed on the computer, as well as provide means for embedding the font into the PDF so that it can be opened on systems where the font is not installed. (It also provides the capabilities to the Windows <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>png()</code></a>
 device).</p>
</li>
<li>
<p><strong>sysfonts</strong> and <strong>showtext</strong>. These packages are developed by Yixuan Qiu and provide support for system fonts to all graphics devices, by hijacking the <a href="https://rdrr.io/r/graphics/text.html" target="_blank" rel="noopener"><code>text()</code></a>
 method of the graphics device to treat text as polygons or raster images. This guarantees your plots will look the same on every device, but it doesn&rsquo;t do advanced text shaping, so there&rsquo;s no support for ligatures or font substitution. Additionally, it produces large files with inaccessible text when used to produce pdf and svg outputs.</p>
</li>
<li>
<p><strong>systemfonts</strong> and <strong>textshaping</strong>. These packages are developed by me to provide a soup-to-nuts solution to text rendering for graphics devices. <a href="https://systemfonts.r-lib.org" target="_blank" rel="noopener">systemfonts</a>
 provides access to fonts installed on the system along with font fallback mechanisms, registration of non-system fonts, reading of font files etc. <a href="https://github.com/r-lib/textshaping" target="_blank" rel="noopener">textshaping</a>
 builds on top of systemfonts and provides a fully modern engine for shaping text. The functionality is exposed both at the R level and at the C level, so that graphics devices can directly access to font lookup and shaping.</p>
</li>
</ul>
<p>We will fosus on systemfonts, because it&rsquo;s designed to give R a modern text rendering stack. That&rsquo;s unfortunately impossible without coordination with the graphics device, which means that to use all these features you need a supported graphics device. There are currently two options:</p>
<ul>
<li>The <a href="https://ragg.r-lib.org" target="_blank" rel="noopener">ragg</a>
 package provides graphics devices for rendering raster graphics in a variety of formats (PNG, JPEG, TIFF) and uses systemfonts and textshaping extensively.</li>
<li>The <a href="https://svglite.r-lib.org" target="_blank" rel="noopener">svglite</a>
 package provides a graphic device for rendering vector graphics to SVG using systemfonts and textshaping for text.</li>
</ul>
<p>You might notice there&rsquo;s currently a big hole in this workflow: PDFs. This is something we plan to work on in the future.</p>
<h2 id="a-systemfonts-based-workflow">A systemfonts based workflow
</h2>
<p>With all that said, how do you actually use systemfonts to use custom fonts in your plots? First, you&rsquo;ll need to use ragg or svglite.</p>
<h3 id="using-ragg">Using ragg
</h3>
<p>While there is no way to unilaterally make <a href="https://ragg.r-lib.org/reference/agg_png.html" target="_blank" rel="noopener"><code>ragg::agg_png()</code></a>
 the default everywhere, it&rsquo;s possible to get close:</p>
<ul>
<li>
<p>Positron: recent versions automatically use ragg for the plot pane if it&rsquo;s installed.</p>
</li>
<li>
<p>RStudio IDE: set &ldquo;AGG&rdquo; as the backend under Global Options &gt; General &gt; Graphics.</p>
</li>
<li>
<p><a href="https://ggplot2.tidyverse.org/reference/ggsave.html" target="_blank" rel="noopener"><code>ggplot2::ggsave()</code></a>
: ragg will be automatically used for raster output if installed.</p>
</li>
<li>
<p>R Markdown and Quarto: you need to set the <code>dev</code> option to <code>&quot;ragg_png&quot;</code>. You can either do this with code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#| include: false</span>
</span></span><span class="line"><span class="cl"><span class="n">knitr</span><span class="o">::</span><span class="n">opts_chunk</span><span class="o">$</span><span class="nf">set</span><span class="p">(</span><span class="n">dev</span> <span class="o">=</span> <span class="s">&#34;ragg_png&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Or in Quarto, you can set it in the yaml metadata:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;My Document&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="l">html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">knitr</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">opts_chunk</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">dev</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;ragg_png&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<p>If you want to use a font installed on your computer, you&rsquo;re done!</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.text.html'>grid.text</a></span><span class='o'>(</span></span>
<span>  <span class='s'>"FUTURA 🎉"</span>,</span>
<span>  gp <span class='o'>=</span> <span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/gpar.html'>gpar</a></span><span class='o'>(</span>fontfamily <span class='o'>=</span> <span class='s'>"Futura"</span>, fontface <span class='o'>=</span> <span class='m'>3</span>, fontsize <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-7-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<p>Or, if using ggplot2</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://ggplot2.tidyverse.org/reference/ggplot.html'>ggplot</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/stats/na.fail.html'>na.omit</a></span><span class='o'>(</span><span class='nv'>penguins</span><span class='o'>)</span><span class='o'>)</span> <span class='o'>+</span></span>
<span>  <span class='nf'><a href='https://ggplot2.tidyverse.org/reference/geom_point.html'>geom_point</a></span><span class='o'>(</span><span class='nf'><a href='https://ggplot2.tidyverse.org/reference/aes.html'>aes</a></span><span class='o'>(</span>x <span class='o'>=</span> <span class='nv'>bill_len</span>, y <span class='o'>=</span> <span class='nv'>body_mass</span>, colour <span class='o'>=</span> <span class='nv'>species</span><span class='o'>)</span><span class='o'>)</span> <span class='o'>+</span></span>
<span>  <span class='nf'><a href='https://ggplot2.tidyverse.org/reference/labs.html'>labs</a></span><span class='o'>(</span>x <span class='o'>=</span> <span class='s'>"Bill Length"</span>, y <span class='o'>=</span> <span class='s'>"Body Mass"</span>, colour <span class='o'>=</span> <span class='s'>"Species"</span><span class='o'>)</span> <span class='o'>+</span></span>
<span>  <span class='nf'><a href='https://ggplot2.tidyverse.org/reference/ggtheme.html'>theme_minimal</a></span><span class='o'>(</span>base_family <span class='o'>=</span> <span class='s'>"Futura"</span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-8-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<p>If the results don&rsquo;t look as you expect, you can use various systemfonts helpers to diagnose the problem:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/match_fonts.html'>match_fonts</a></span><span class='o'>(</span><span class='s'>"Futura"</span>, weight <span class='o'>=</span> <span class='s'>"bold"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 1 × 3</span></span></span>
<span><span class='c'>#&gt;   path                                          index features  </span></span>
<span><span class='c'>#&gt;   <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>                                         <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;list&gt;</span>    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>1</span> /System/Library/Fonts/Supplemental/Futura.ttc     2 <span style='color: #555555;'>&lt;font_ftr&gt;</span></span></span>
<span></span><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/font_fallback.html'>font_fallback</a></span><span class='o'>(</span><span class='s'>"🎉"</span>, family <span class='o'>=</span> <span class='s'>"Futura"</span>, weight <span class='o'>=</span> <span class='s'>"bold"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt;                                          path index</span></span>
<span><span class='c'>#&gt; 1 /System/Library/Fonts/Apple Color Emoji.ttc     0</span></span>
<span></span></code></pre>
</div>
<p>If you want to see all the fonts that are available for use, you can use <a href="https://systemfonts.r-lib.org/reference/system_fonts.html" target="_blank" rel="noopener"><code>systemfonts::system_fonts()</code></a>
</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/system_fonts.html'>system_fonts</a></span><span class='o'>(</span><span class='o'>)</span></span></code></pre>
</div>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 570 × 9</span></span></span>
<span><span class='c'>#&gt;    path                                         index name  family style weight width italic monospace</span></span>
<span><span class='c'>#&gt;    <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>                                        <span style='color: #555555; font-style: italic;'>&lt;int&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;ord&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;ord&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;lgl&gt;</span>    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 1</span> /System/Library/Fonts/Supplemental/Rockwell…     2 Rock… Rockw… Bold  bold   norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 2</span> /System/Library/Fonts/Noteworthy.ttc             0 Note… Notew… Light normal norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 3</span> /System/Library/Fonts/Supplemental/Devanaga…     1 Deva… Devan… Bold  bold   norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 4</span> /System/Library/Fonts/Supplemental/Kannada …     0 Kann… Kanna… Regu… normal norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 5</span> /System/Library/Fonts/Supplemental/Verdana …     0 Verd… Verda… Bold  bold   norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 6</span> /System/Library/Fonts/ArialHB.ttc                8 Aria… Arial… Light light  norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 7</span> /System/Library/Fonts/AppleSDGothicNeo.ttc      10 Appl… Apple… Thin  thin   norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 8</span> /System/Library/Fonts/Supplemental/DecoType…     0 Deco… DecoT… Regu… normal norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 9</span> /System/Library/Fonts/Supplemental/Trebuche…     0 Treb… Trebu… Ital… normal norm… TRUE   FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>10</span> /System/Library/Fonts/Supplemental/Khmer MN…     0 Khme… Khmer… Regu… normal norm… FALSE  FALSE    </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># ℹ 560 more rows</span></span></span>
<span></span></code></pre>
</div>
<h3 id="extra-font-styles">Extra font styles
</h3>
<p>As we discussed above, the R interface only allows you to select between four styles: plain, italic, bold, and bold-italic. If you want to use a thin font, you have no way of communicating this wish to the device. To overcome this, systemfonts provides <code>register_variant()</code> which allows you to register a font with a new typeface name. For example, to use the thin font from the Avenir Next typeface you can register it as follows:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/register_variant.html'>register_variant</a></span><span class='o'>(</span></span>
<span>  name <span class='o'>=</span> <span class='s'>"Avenir Thin"</span>,</span>
<span>  family <span class='o'>=</span> <span class='s'>"Avenir Next"</span>,</span>
<span>  weight <span class='o'>=</span> <span class='s'>"thin"</span></span>
<span><span class='o'>)</span></span></code></pre>
</div>
<p>Now you can use Avenir Thin where you would otherwise specify the typeface:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.text.html'>grid.text</a></span><span class='o'>(</span></span>
<span>  <span class='s'>"Thin weight is soo classy"</span>,</span>
<span>  gp <span class='o'>=</span> <span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/gpar.html'>gpar</a></span><span class='o'>(</span>fontfamily <span class='o'>=</span> <span class='s'>"Avenir Thin"</span>, fontsize <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-13-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<p><code>register_variant()</code> also allows you to turn on font features otherwise hidden away:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/register_variant.html'>register_variant</a></span><span class='o'>(</span></span>
<span>  name <span class='o'>=</span> <span class='s'>"Avenir Small Caps"</span>,</span>
<span>  family <span class='o'>=</span> <span class='s'>"Avenir Next"</span>,</span>
<span>  features <span class='o'>=</span> <span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/font_feature.html'>font_feature</a></span><span class='o'>(</span></span>
<span>    letters <span class='o'>=</span> <span class='s'>"small_caps"</span></span>
<span>  <span class='o'>)</span></span>
<span><span class='o'>)</span></span>
<span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.text.html'>grid.text</a></span><span class='o'>(</span></span>
<span>  <span class='s'>"All caps — Small caps"</span>,</span>
<span>  gp <span class='o'>=</span> <span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/gpar.html'>gpar</a></span><span class='o'>(</span>fontfamily <span class='o'>=</span> <span class='s'>"Avenir Small Caps"</span>, fontsize <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-14-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<h3 id="fonts-from-other-places">Fonts from other places
</h3>
<p>Historically, systemfonts primary role was to access the font installed on your computer, the <strong>system fonts</strong>. But what if you&rsquo;re using a computer where you don&rsquo;t have the rights to install new fonts, or you don&rsquo;t want the hassle of installing a font just to use it for a single plot? That&rsquo;s the problem solved by <code>systemfonts::add_font()</code> which makes it easy to use a font based on a path. But in many cases you don&rsquo;t even need that as systemfont now scans <code>./fonts</code> and <code>~/fonts</code> and adds any font files it find. This means that you can put personal fonts in a fonts folder in your home directory, and project fonts in a fonts directory at the root of the project. This is a great way to ensure that specific fonts are available when you deploy some code to a server.</p>
<p>And you don&rsquo;t even need to leave R to populate these folders. <a href="https://systemfonts.r-lib.org/reference/web-fonts.html" target="_blank" rel="noopener"><code>systemfonts::get_from_google_fonts()</code></a>
 will download and install a google font in <code>~/fonts</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/web-fonts.html'>get_from_google_fonts</a></span><span class='o'>(</span><span class='s'>"Barrio"</span><span class='o'>)</span></span>
<span></span>
<span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.text.html'>grid.text</a></span><span class='o'>(</span></span>
<span>  <span class='s'>"A new font a day keeps Tufte away"</span>,</span>
<span>  gp <span class='o'>=</span> <span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/gpar.html'>gpar</a></span><span class='o'>(</span>fontfamily <span class='o'>=</span> <span class='s'>"Barrio"</span>, fontsize <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-15-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<p>And if you want to make sure this code works for anyone using your code (regardless of whether or not they already have the font installed), you can use <a href="https://systemfonts.r-lib.org/reference/require_font.html" target="_blank" rel="noopener"><code>systemfonts::require_font()</code></a>
. If the font isn&rsquo;t already installed, this function download it from one of the repositories it knows about. If it can&rsquo;t find it it will either throw an error (the default) or remap the name to another font so that plotting will still succeed.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/require_font.html'>require_font</a></span><span class='o'>(</span><span class='s'>"Rubik Distressed"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Trying Google Fonts... Found! Downloading font to /var/folders/l4/tvfrd0ps4dqdr2z7kvnl9xh40000gn/T//Rtmp2qw4bE</span></span>
<span></span><span></span>
<span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.text.html'>grid.text</a></span><span class='o'>(</span></span>
<span>  <span class='s'>"There are no bad fonts\nonly bad text"</span>,</span>
<span>  gp <span class='o'>=</span> <span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/gpar.html'>gpar</a></span><span class='o'>(</span>fontfamily <span class='o'>=</span> <span class='s'>"Rubik Distressed"</span>, fontsize <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-16-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<p>By default, <code>require_font()</code> places new fonts in a temporary folder so it doesn&rsquo;t pollute your carefully curated collection of fonts.</p>
<h3 id="font-embedding-in-svg">Font embedding in SVG
</h3>
<p>Fonts work a little differently in vector formats like SVG. These formats include the raw text and only render the font when you open the file. This makes for small, accessible files with crisp text at every level of zoom. But it comes with a price: since the text is rendered when it&rsquo;s opened, it relies on the font in use being available on the viewer&rsquo;s computer. This obviously puts you at the mercy of their font selection, so if you want consistent outputs you&rsquo;ll need to <strong>embed</strong> the font.</p>
<p>In SVG, you can embed fonts using an <code>@import</code> statement in the stylesheet, and can point to a web resource so the SVG doesn&rsquo;t need to contain the entire font. systemfonts provides facilities to generate URLs for import statements and can provide them in a variety of formats:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/fonts_as_import.html'>fonts_as_import</a></span><span class='o'>(</span><span class='s'>"Barrio"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] "https://fonts.bunny.net/css2?family=Barrio&amp;display=swap"</span></span>
<span></span><span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/fonts_as_import.html'>fonts_as_import</a></span><span class='o'>(</span><span class='s'>"Rubik Distressed"</span>, type <span class='o'>=</span> <span class='s'>"link"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] "&lt;link rel=\"stylesheet\" href=\"https://fonts.bunny.net/css2?family=Rubik+Distressed&amp;display=swap\"/&gt;"</span></span>
<span></span></code></pre>
</div>
<p>Further, if the font is not available from an online repository, it can embed the font data directly into the URL:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/substr.html'>substr</a></span><span class='o'>(</span><span class='nf'>systemfonts</span><span class='nf'>::</span><span class='nf'><a href='https://systemfonts.r-lib.org/reference/fonts_as_import.html'>fonts_as_import</a></span><span class='o'>(</span><span class='s'>"Chalkduster"</span><span class='o'>)</span>, <span class='m'>1</span>, <span class='m'>200</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] "data:text/css,@font-face%20%7B%0A%20%20font-family:%20%22Chalkduster%22;%0A%20%20src:%20url(data:font/ttf;charset=utf-8;base64,AAEAAAAMAIAAAwC4T1MvMmk8+wsAAAFIAAAAYGNtYXBJhgfNAAAEOAAACspnbHlmLDPYGwAAf"</span></span>
<span></span></code></pre>
</div>
<p>svglite uses this feature to allow seamless font embedding with the <code>web_fonts</code> argument. It can take a URL as returned by <code>fonts_as_import()</code> or just the name of the typeface and the URL will automatically be resolved. Look at line 6 in the SVG generated below</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>svg</span> <span class='o'>&lt;-</span> <span class='nf'>svglite</span><span class='nf'>::</span><span class='nf'><a href='https://svglite.r-lib.org/reference/svgstring.html'>svgstring</a></span><span class='o'>(</span>web_fonts <span class='o'>=</span> <span class='s'>"Barrio"</span><span class='o'>)</span></span>
<span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.text.html'>grid.text</a></span><span class='o'>(</span><span class='s'>"Example"</span>, gp <span class='o'>=</span> <span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/gpar.html'>gpar</a></span><span class='o'>(</span>fontfamily <span class='o'>=</span> <span class='s'>"Barrio"</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/invisible.html'>invisible</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/grDevices/dev.html'>dev.off</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/grDevices/cairo.html'>svg</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; &lt;?xml version='1.0' encoding='UTF-8' ?&gt;</span></span>
<span><span class='c'>#&gt; &lt;svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='720.00pt' height='576.00pt' viewBox='0 0 720.00 576.00'&gt;</span></span>
<span><span class='c'>#&gt; &lt;g class='svglite'&gt;</span></span>
<span><span class='c'>#&gt; &lt;defs&gt;</span></span>
<span><span class='c'>#&gt;   &lt;style type='text/css'&gt;&lt;![CDATA[</span></span>
<span><span class='c'>#&gt;     @import url('https://fonts.bunny.net/css2?family=Barrio&amp;display=swap');</span></span>
<span><span class='c'>#&gt;     .svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle &#123;</span></span>
<span><span class='c'>#&gt;       fill: none;</span></span>
<span><span class='c'>#&gt;       stroke: #000000;</span></span>
<span><span class='c'>#&gt;       stroke-linecap: round;</span></span>
<span><span class='c'>#&gt;       stroke-linejoin: round;</span></span>
<span><span class='c'>#&gt;       stroke-miterlimit: 10.00;</span></span>
<span><span class='c'>#&gt;     &#125;</span></span>
<span><span class='c'>#&gt;     .svglite text &#123;</span></span>
<span><span class='c'>#&gt;       white-space: pre;</span></span>
<span><span class='c'>#&gt;     &#125;</span></span>
<span><span class='c'>#&gt;     .svglite g.glyphgroup path &#123;</span></span>
<span><span class='c'>#&gt;       fill: inherit;</span></span>
<span><span class='c'>#&gt;       stroke: none;</span></span>
<span><span class='c'>#&gt;     &#125;</span></span>
<span><span class='c'>#&gt;   ]]&gt;&lt;/style&gt;</span></span>
<span><span class='c'>#&gt; &lt;/defs&gt;</span></span>
<span><span class='c'>#&gt; &lt;rect width='100%' height='100%' style='stroke: none; fill: #FFFFFF;'/&gt;</span></span>
<span><span class='c'>#&gt; &lt;defs&gt;</span></span>
<span><span class='c'>#&gt;   &lt;clipPath id='cpMC4wMHw3MjAuMDB8MC4wMHw1NzYuMDA='&gt;</span></span>
<span><span class='c'>#&gt;     &lt;rect x='0.00' y='0.00' width='720.00' height='576.00' /&gt;</span></span>
<span><span class='c'>#&gt;   &lt;/clipPath&gt;</span></span>
<span><span class='c'>#&gt; &lt;/defs&gt;</span></span>
<span><span class='c'>#&gt; &lt;g clip-path='url(#cpMC4wMHw3MjAuMDB8MC4wMHw1NzYuMDA=)'&gt;</span></span>
<span><span class='c'>#&gt; &lt;text x='360.00' y='292.32' text-anchor='middle' style='font-size: 12.00px; font-family: "Barrio";' textLength='48.12px' lengthAdjust='spacingAndGlyphs'&gt;Example&lt;/text&gt;</span></span>
<span><span class='c'>#&gt; &lt;/g&gt;</span></span>
<span><span class='c'>#&gt; &lt;/g&gt;</span></span>
<span><span class='c'>#&gt; &lt;/svg&gt;</span></span>
<span></span></code></pre>
</div>
<h2 id="want-more">Want more?
</h2>
<p>This document has mainly focused on how to use the fonts you desire from within R. R has other limitations when it comes to text rendering specifically how to render text that consists of a mix of fonts. This has been solved by <a href="https://marquee.r-lib.org" target="_blank" rel="noopener">marquee</a>
 and the curious soul can continue there in order to up their skills in rendering text with R</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>grid</span><span class='nf'>::</span><span class='nf'><a href='https://rdrr.io/r/grid/grid.draw.html'>grid.draw</a></span><span class='o'>(</span></span>
<span>  <span class='nf'>marquee</span><span class='nf'>::</span><span class='nf'><a href='https://marquee.r-lib.org/reference/marquee_grob.html'>marquee_grob</a></span><span class='o'>(</span></span>
<span>    <span class='s'>"_This_ **is** the &#123;.red end&#125;"</span>,</span>
<span>    <span class='nf'>marquee</span><span class='nf'>::</span><span class='nf'><a href='https://marquee.r-lib.org/reference/classic_style.html'>classic_style</a></span><span class='o'>(</span>base_size <span class='o'>=</span> <span class='m'>30</span><span class='o'>)</span></span>
<span>  <span class='o'>)</span></span>
<span><span class='o'>)</span></span>
</code></pre>
<img src="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/figs/unnamed-chunk-20-1.png" width="700px" style="display: block; margin: auto;" />
</div>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Be aware that the style name is at the discretion of the developer of the typeface. It is very common to see discrepancies between the style name and e.g. the weight reported by the font (e.g. Avenir Next Ultra Light is a <em>thin</em> weight font).&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2025/fonts-in-r/thumbnail-wd.jpg" length="324850" type="image/jpeg" />
    </item>
    <item>
      <title>S7 0.2.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2024/s7-0-2-0/</link>
      <pubDate>Thu, 07 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2024/s7-0-2-0/</guid>
      <dc:creator>Tomasz Kalinowski</dc:creator>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<!--
TODO:
* [ ] Look over / edit the post's title in the yaml
* [ ] Edit (or delete) the description; note this appears in the Twitter card
* [ ] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [ ] Find photo & update yaml metadata
* [ ] Create `thumbnail-sq.jpg`; height and width should be equal
* [ ] Create `thumbnail-wd.jpg`; width should be >5x height
* [ ] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [ ] Add intro sentence, e.g. the standard tagline for the package
* [ ] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re excited to announce that <a href="https://rconsortium.github.io/S7/" target="_blank" rel="noopener">S7</a>
 v0.2.0 is now available on CRAN! S7 is a new object-oriented programming (OOP) system designed to supersede both S3 and S4. You might wonder why R needs a new OOP system when we already have two. The reason lies in the history of R&rsquo;s OOP journey: S3 is a simple and effective system for single dispatch, while S4 adds formal class definitions and multiple dispatch, but at the cost of complexity. This has forced developers to choose between the simplicity of S3 and the sophistication of S4.</p>
<p>The goal of S7 is to unify the OOP landscape by building on S3&rsquo;s existing dispatch system and incorporating the most useful features of S4 (along with some new ones), all with a simpler syntax. S7&rsquo;s design and implementation have been a collaborative effort by a working group from the <a href="https://www.r-consortium.org" target="_blank" rel="noopener">R Consortium</a>
, including representatives from R-Core, Bioconductor, tidyverse/Posit, ROpenSci, and the wider R community. Since S7 builds on S3, it is fully compatible with existing S3-based code. It&rsquo;s also been thoughtfully designed to work with S4, and as we learn more about the challenges of transitioning from S4 to S7, we&rsquo;ll continue to add features to ease this process.</p>
<p>Our long-term goal is to include S7 in base R, but for now, you can install it from CRAN:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"S7"</span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="whats-new-in-the-second-release">What&rsquo;s new in the second release
</h2>
<p>The second release of S7 brings refinements and bug fixes. Highlights include:</p>
<ul>
<li>Support for lazy property defaults, making class setup more flexible.</li>
<li>Custom property setters now run on object initialization.</li>
<li>Significant speed improvements for setting and getting properties with <code>@</code> and <code>@&lt;-</code>.</li>
<li>Expanded compatibility with base S3 classes.</li>
<li><a href="https://rconsortium.github.io/S7/reference/convert.html" target="_blank" rel="noopener"><code>convert()</code></a>
 now provides a default method for transforming a parent class into a subclass.</li>
</ul>
<p>Additionally, there are numerous bug fixes and quality-of-life improvements, such as better error messages, improved support for base Ops methods, and compatibility improvements for using <code>@</code> in R versions prior to 4.3. You can see a full list of changes in the <a href="https://github.com/RConsortium/S7/blob/main/NEWS.md" target="_blank" rel="noopener">release notes</a>
.</p>
<h2 id="who-should-use-s7">Who should use S7
</h2>
<p>S7 is a great fit for R users who like to try new things but don&rsquo;t need to be the first. It&rsquo;s already used in several CRAN packages, and the tidyverse team is applying it in new projects. While you may still run into a few issues, many early problems have been resolved.</p>
<h2 id="usage">Usage
</h2>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://rconsortium.github.io/S7/'>S7</a></span><span class='o'>)</span></span></code></pre>
</div>
<p>Let&rsquo;s dive into the basics of S7. To learn more, check out the package vignettes, including a more detailed introduction in <a href="https://rconsortium.github.io/S7/articles/S7.html" target="_blank" rel="noopener"><code>vignette(&quot;S7&quot;)</code></a>
, and coverage of generics and methods in <a href="https://rconsortium.github.io/S7/articles/generics-methods.html" target="_blank" rel="noopener"><code>vignette(&quot;generics-methods&quot;)</code></a>
, and classes and objects in <a href="https://rconsortium.github.io/S7/articles/classes-objects.html" target="_blank" rel="noopener"><code>vignette(&quot;classes-objects&quot;)</code></a>
.</p>
<h3 id="classes-and-objects">Classes and objects
</h3>
<p>S7 classes have formal definitions, specified by <a href="https://rconsortium.github.io/S7/reference/new_class.html" target="_blank" rel="noopener"><code>new_class()</code></a>
, which includes a list of properties and an optional validator. For example, the following code creates a <code>Range</code> class with <code>start</code> and <code>end</code> properties, and a validator to ensure that <code>start</code> is always less than <code>end</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>Range</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rconsortium.github.io/S7/reference/new_class.html'>new_class</a></span><span class='o'>(</span><span class='s'>"Range"</span>,</span>
<span>  properties <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span></span>
<span>    start <span class='o'>=</span> <span class='nv'>class_double</span>,</span>
<span>    end <span class='o'>=</span> <span class='nv'>class_double</span></span>
<span>  <span class='o'>)</span>,</span>
<span>  validator <span class='o'>=</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>self</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>    <span class='kr'>if</span> <span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/length.html'>length</a></span><span class='o'>(</span><span class='nv'>self</span><span class='o'>@</span><span class='nv'>start</span><span class='o'>)</span> <span class='o'>!=</span> <span class='m'>1</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>      <span class='s'>"@start must be length 1"</span></span>
<span>    <span class='o'>&#125;</span> <span class='kr'>else</span> <span class='kr'>if</span> <span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/length.html'>length</a></span><span class='o'>(</span><span class='nv'>self</span><span class='o'>@</span><span class='nv'>end</span><span class='o'>)</span> <span class='o'>!=</span> <span class='m'>1</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>      <span class='s'>"@end must be length 1"</span></span>
<span>    <span class='o'>&#125;</span> <span class='kr'>else</span> <span class='kr'>if</span> <span class='o'>(</span><span class='nv'>self</span><span class='o'>@</span><span class='nv'>end</span> <span class='o'>&lt;</span> <span class='nv'>self</span><span class='o'>@</span><span class='nv'>start</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>      <span class='s'>"@end must be greater than or equal to @start"</span></span>
<span>    <span class='o'>&#125;</span></span>
<span>  <span class='o'>&#125;</span></span>
<span><span class='o'>)</span></span></code></pre>
</div>
<p><a href="https://rconsortium.github.io/S7/reference/new_class.html" target="_blank" rel="noopener"><code>new_class()</code></a>
 returns the class object, which also serves as the constructor to create instances of the class:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='nf'>Range</span><span class='o'>(</span>start <span class='o'>=</span> <span class='m'>1</span>, end <span class='o'>=</span> <span class='m'>10</span><span class='o'>)</span></span>
<span><span class='nv'>x</span></span>
<span><span class='c'>#&gt; &lt;Range&gt;</span></span>
<span><span class='c'>#&gt;  @ start: num 1</span></span>
<span><span class='c'>#&gt;  @ end  : num 10</span></span>
<span></span></code></pre>
</div>
<h3 id="properties">Properties
</h3>
<p>The data an object holds are called its <strong>properties</strong>. Use <code>@</code> to get and set properties:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>x</span><span class='o'>@</span><span class='nv'>start</span></span>
<span><span class='c'>#&gt; [1] 1</span></span>
<span></span><span><span class='nv'>x</span><span class='o'>@</span><span class='nv'>end</span> <span class='o'>&lt;-</span> <span class='m'>20</span></span>
<span><span class='nv'>x</span></span>
<span><span class='c'>#&gt; &lt;Range&gt;</span></span>
<span><span class='c'>#&gt;  @ start: num 1</span></span>
<span><span class='c'>#&gt;  @ end  : num 20</span></span>
<span></span></code></pre>
</div>
<p>Properties are automatically validated against the type declared in <a href="https://rconsortium.github.io/S7/reference/new_class.html" target="_blank" rel="noopener"><code>new_class()</code></a>
 (in this case, <code>double</code>) and checked by the class <strong>validator</strong>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>x</span><span class='o'>@</span><span class='nv'>end</span> <span class='o'>&lt;-</span> <span class='s'>"x"</span></span>
<span><span class='c'>#&gt; Error: &lt;Range&gt;@end must be &lt;double&gt;, not &lt;character&gt;</span></span>
<span></span><span><span class='nv'>x</span><span class='o'>@</span><span class='nv'>end</span> <span class='o'>&lt;-</span> <span class='o'>-</span><span class='m'>1</span></span>
<span><span class='c'>#&gt; Error: &lt;Range&gt; object is invalid:</span></span>
<span><span class='c'>#&gt; - @end must be greater than or equal to @start</span></span>
<span></span></code></pre>
</div>
<h3 id="generics-and-methods">Generics and methods
</h3>
<p>Like S3 and S4, S7 uses <strong>functional OOP</strong>, where methods belong to <strong>generic</strong> functions, and method calls look like regular function calls: <code>generic(object, arg2, arg3)</code>. A generic uses the types of its arguments to automatically pick the appropriate method implementation.</p>
<p>You can create a new generic with <a href="https://rconsortium.github.io/S7/reference/new_generic.html" target="_blank" rel="noopener"><code>new_generic()</code></a>
, specifying the arguments to dispatch on:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>inside</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rconsortium.github.io/S7/reference/new_generic.html'>new_generic</a></span><span class='o'>(</span><span class='s'>"inside"</span>, <span class='s'>"x"</span><span class='o'>)</span></span></code></pre>
</div>
<p>To define a method for a specific class, use <code>method(generic, class) &lt;- implementation</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rconsortium.github.io/S7/reference/method.html'>method</a></span><span class='o'>(</span><span class='nv'>inside</span>, <span class='nv'>Range</span><span class='o'>)</span> <span class='o'>&lt;-</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>x</span>, <span class='nv'>y</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nv'>y</span> <span class='o'>&gt;=</span> <span class='nv'>x</span><span class='o'>@</span><span class='nv'>start</span> <span class='o'>&amp;</span> <span class='nv'>y</span> <span class='o'>&lt;=</span> <span class='nv'>x</span><span class='o'>@</span><span class='nv'>end</span></span>
<span><span class='o'>&#125;</span></span>
<span></span>
<span><span class='nf'>inside</span><span class='o'>(</span><span class='nv'>x</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='m'>0</span>, <span class='m'>5</span>, <span class='m'>10</span>, <span class='m'>15</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] FALSE  TRUE  TRUE  TRUE</span></span>
<span></span></code></pre>
</div>
<p>Printing the generic shows its methods:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>inside</span></span>
<span><span class='c'>#&gt; &lt;S7_generic&gt; inside(x, ...) with 1 methods:</span></span>
<span><span class='c'>#&gt; 1: method(inside, Range)</span></span>
<span></span></code></pre>
</div>
<p>And you can retrieve the method for a specific class:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rconsortium.github.io/S7/reference/method.html'>method</a></span><span class='o'>(</span><span class='nv'>inside</span>, <span class='nv'>Range</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; &lt;S7_method&gt; method(inside, Range)</span></span>
<span><span class='c'>#&gt; function (x, y) </span></span>
<span><span class='c'>#&gt; &#123;</span></span>
<span><span class='c'>#&gt;     y &gt;= x@start &amp; y &lt;= x@end</span></span>
<span><span class='c'>#&gt; &#125;</span></span>
<span></span></code></pre>
</div>
<h2 id="known-limitations">Known limitations
</h2>
<p>While we are pleased with S7&rsquo;s design, there are still some limitations:</p>
<ul>
<li>S7 objects can be serialized to disk (with <a href="https://rdrr.io/r/base/readRDS.html" target="_blank" rel="noopener"><code>saveRDS()</code></a>
), but the current implementation saves the entire class specification with each object. This may change in the future.</li>
<li>Support for implicit S3 classes <code>&quot;array&quot;</code> and <code>&quot;matrix&quot;</code> is still in development.</li>
</ul>
<p>We expect the community will uncover more issues as S7 is more widely adopted. If you encounter any problems, please file an issue at <a href="https://github.com/RConsortium/OOP-WG/issues" target="_blank" rel="noopener">https://github.com/RConsortium/OOP-WG/issues</a>
. We appreciate your feedback in helping us make S7 even better! 😃</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>Thank you to all people who have contributed issues, code, and comments to this release:</p>
<p><a href="https://github.com/calderonsamuel" target="_blank" rel="noopener">@calderonsamuel</a>
, <a href="https://github.com/Crosita" target="_blank" rel="noopener">@Crosita</a>
, <a href="https://github.com/DavisVaughan" target="_blank" rel="noopener">@DavisVaughan</a>
, <a href="https://github.com/dipterix" target="_blank" rel="noopener">@dipterix</a>
, <a href="https://github.com/guslipkin" target="_blank" rel="noopener">@guslipkin</a>
, <a href="https://github.com/gvelasq" target="_blank" rel="noopener">@gvelasq</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/jeffkimbrel" target="_blank" rel="noopener">@jeffkimbrel</a>
, <a href="https://github.com/jl5000" target="_blank" rel="noopener">@jl5000</a>
, <a href="https://github.com/jmbarbone" target="_blank" rel="noopener">@jmbarbone</a>
, <a href="https://github.com/jmiahjones" target="_blank" rel="noopener">@jmiahjones</a>
, <a href="https://github.com/jonthegeek" target="_blank" rel="noopener">@jonthegeek</a>
, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>
, <a href="https://github.com/jtlandis" target="_blank" rel="noopener">@jtlandis</a>
, <a href="https://github.com/lawremi" target="_blank" rel="noopener">@lawremi</a>
, <a href="https://github.com/MarcellGranat" target="_blank" rel="noopener">@MarcellGranat</a>
, <a href="https://github.com/mikmart" target="_blank" rel="noopener">@mikmart</a>
, <a href="https://github.com/mmaechler" target="_blank" rel="noopener">@mmaechler</a>
, <a href="https://github.com/mynanshan" target="_blank" rel="noopener">@mynanshan</a>
, <a href="https://github.com/rikivillalba" target="_blank" rel="noopener">@rikivillalba</a>
, <a href="https://github.com/sjcowtan" target="_blank" rel="noopener">@sjcowtan</a>
, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>
, <a href="https://github.com/teunbrand" target="_blank" rel="noopener">@teunbrand</a>
, and <a href="https://github.com/waynelapierre" target="_blank" rel="noopener">@waynelapierre</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2024/s7-0-2-0/thumbnail-wd.jpg" length="78819" type="image/jpeg" />
    </item>
    <item>
      <title>pkgdown 2.1.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2024/pkgdown-2-1-0/</link>
      <pubDate>Mon, 08 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2024/pkgdown-2-1-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [s] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re delighted to announce the release of <a href="http://pkgdown.r-lib.org/" target="_blank" rel="noopener">pkgdown</a>
 2.1.0. pkgdown is designed to make it quick and easy to build a beautiful and accessible website for your package.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"pkgdown"</span><span class='o'>)</span></span></code></pre>
</div>
<p>This is a massive release with a bunch of new features. I&rsquo;ll highlight the most important here, but as always, I highlight recommend skimming the <a href="https://github.com/r-lib/pkgdown/releases/tag/v2.1.0" target="_blank" rel="noopener">release notes</a>
 for other smaller improvements and bug fixes.</p>
<p>First, and most importantly, please join me in welcoming two new authors to pkgdown: <a href="https://github.com/olivroy" target="_blank" rel="noopener">Olivier Roy</a>
 and <a href="https://github.com/salim-b" target="_blank" rel="noopener">Salim Brüggemann</a>
. They have both contributed many improvements to the package and I&rsquo;m very happy to officially have them aboard as package authors.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://pkgdown.r-lib.org/'>pkgdown</a></span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="lifecycle-changes">Lifecycle changes
</h2>
<p>Let&rsquo;s get started with the important stuff, the <a href="https://www.tidyverse.org/blog/2021/02/lifecycle-1-0-0/" target="_blank" rel="noopener">lifecycle updates</a>
. Most important we&rsquo;ve decided to deprecate support for Bootstrap 3, which was superseded in December 2021. We&rsquo;re starting to more directly encourage folks to move away from it as maintaining two separate sets of site templates is a time sink. If you&rsquo;re still using BS3, now&rsquo;s the <a href="https://www.tidyverse.org/blog/2021/12/pkgdown-2-0-0/#bootstrap-5" target="_blank" rel="noopener">time to upgrade</a>
.</p>
<p>There are three other changes that are less likely to affect folks:</p>
<ul>
<li>
<p>The <code>document</code> argument to <a href="https://pkgdown.r-lib.org/reference/build_site.html" target="_blank" rel="noopener"><code>build_site()</code></a>
 and <a href="https://pkgdown.r-lib.org/reference/build_reference.html" target="_blank" rel="noopener"><code>build_reference()</code></a>
 has been removed after being deprecated in pkgdown 1.4.0; use the <a href="https://pkgdown.r-lib.org/reference/build_site.html#arg-devel" target="_blank" rel="noopener"><code>devel</code> argument</a>
 instead.</p>
</li>
<li>
<p><a href="https://pkgdown.r-lib.org/reference/autolink_html.html" target="_blank" rel="noopener"><code>autolink_html()</code></a>
 was deprecated in pkgdown 1.6.0 and now warns every time you use it; use <a href="https://downlit.r-lib.org/reference/downlit_html_path.html" target="_blank" rel="noopener"><code>downlit::downlit_html_path()</code></a>
 instead.</p>
</li>
<li>
<p><a href="https://pkgdown.r-lib.org/reference/preview_page.html" target="_blank" rel="noopener"><code>preview_page()</code></a>
 has been deprecated; use <a href="https://pkgdown.r-lib.org/reference/preview_site.html" target="_blank" rel="noopener"><code>preview_site()</code></a>
 instead.</p>
</li>
</ul>
<h2 id="major-new-features">Major new features
</h2>
<p>pkgdown 2.1.0 has two major new features: support for Quarto vignettes and a new light switch that toggles between light and dark modes.</p>
<h3 id="quarto-support">Quarto support
</h3>
<p><a href="https://pkgdown.r-lib.org/reference/build_articles.html" target="_blank" rel="noopener"><code>build_article()</code></a>
/<a href="https://pkgdown.r-lib.org/reference/build_articles.html" target="_blank" rel="noopener"><code>build_articles()</code></a>
 now support articles and vignettes written with Quarto. To use it, make sure you have the the latest version of Quarto, 1.5, which was released last week. By and large you should be able to just write in Quarto and things will just work, but you will need to make a small change to your GitHub action. Learn more at <a href="https://pkgdown.r-lib.org/articles/quarto.html" target="_blank" rel="noopener"><code>vignette(&quot;quarto&quot;)</code></a>
.</p>
<p>Combining the individual quarto and pkgdown templating systems is a delicate art, so while I&rsquo;ve done my best to make it work, there may be some rough edges. Check out the current known limitations in <a href="https://pkgdown.r-lib.org/articles/quarto.html" target="_blank" rel="noopener"><code>vignette(&quot;quarto&quot;)</code></a>
, and please file an issue if you encounter a quarto feature that doesn&rsquo;t work quite right.</p>
<h3 id="light-switch">Light switch
</h3>
<p>pkgdown sites can now provide a &ldquo;light switch&rdquo; that allows the reader to switch between light and dark modes (based on work in bslib by <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>
). You can try it out on <a href="https://pkgdown.r-lib.org" target="_blank" rel="noopener">https://pkgdown.r-lib.org</a>
: the light switch appears at the far right at the navbar and remembers the users choice between visits to your site.</p>
<p>(Note that the light switch works differently to quarto dark mode. In quarto, you can provide two completely different themes for light and dark mode. In pkgdown, dark mode is a relatively thin overlay that based on your light theme colours.)</p>
<p>For now, you&rsquo;ll need to opt-in to the light-switch by adding the following to your <code>_pkgdown.yml</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">light-switch</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>In the future we hope to turn it on automatically.</p>
<p>You can learn more about customising the light switch in <a href="https://pkgdown.r-lib.org/articles/customise.html" target="_blank" rel="noopener"><code>vignette(&quot;customise&quot;)</code></a>
: you can choose to select your own syntax highlighting scheme for dark mode, override dark-specific BS lib variables, and move its location in the navbar.</p>
<h2 id="user-experience">User experience
</h2>
<p>We&rsquo;ve made a bunch of small changes to enhance the user experience of pkgdown sites:</p>
<ul>
<li>
<p>We&rsquo;ve continued in our efforts to make pkgdown sites as accessible as possible by now warning if you&rsquo;ve forgotten to add alt text to images (including plots) in your articles. We&rsquo;ve also added a new <a href="https://pkgdown.r-lib.org/articles/accessibility.html" target="_blank" rel="noopener"><code>vignette(&quot;accessibility&quot;)</code></a>
 which describes additional manual tasks you can perform to make your site as accessible as possible.</p>
</li>
<li>
<p><a href="https://pkgdown.r-lib.org/reference/build_reference.html" target="_blank" rel="noopener"><code>build_reference()</code></a>
 adds anchors to arguments making it possible to link directly to an argument. This is very useful when you&rsquo;re trying to direct folks to the documentation for a specific argument, e.g. <a href="https://pkgdown.r-lib.org/reference/build_site.html#arg-devel" target="_blank" rel="noopener">https://pkgdown.r-lib.org/reference/build_site.html#arg-devel</a>
.</p>
</li>
<li>
<p><a href="https://pkgdown.r-lib.org/reference/build_reference.html" target="_blank" rel="noopener"><code>build_reference_index()</code></a>
 now displays function lifecycle badges <a href="https://pkgdown.r-lib.org/reference/index.html#deprecated-functions" target="_blank" rel="noopener">next to the function name</a>
. If you want to gather together (e.g.) all the deprecated function in one spot in the reference index, you can use the new topic selector <code>has_lifecycle(&quot;deprecated&quot;)</code>.</p>
</li>
<li>
<p>The new <code>template.math-rendering</code> option allows you to control how math is rendered on your site. The default uses <code>mathml</code> which is zero dependency but has the lowest fidelity. If you use a lot of math on your site, you can switch back to the previous method with <code>mathjax</code>, or try out <code>katex</code>, a faster alternative.</p>
</li>
<li>
<p>pkgdown sites no longer depend on external content distribution networks (CDN) for common javascript, CSS, and font files. CDNs no longer provide <a href="https://www.stefanjudis.com/notes/say-goodbye-to-resource-caching-across-sites-and-domains/" target="_blank" rel="noopener">any performance advantages</a>
 and make deployment harder inside certain locked-down corporate environments.</p>
</li>
<li>
<p>pkgdown includes translations for more terms including &ldquo;Abstract&rdquo; and &ldquo;Search site&rdquo;. A big thanks to @jplecavalier, @dieghernan, @krlmlr, @LDalby, @rich-iannone, @jmaspons, and @mine-cetinkaya-rundel for providing updated translations in French, Spanish, Portugese, Germna, Catalan, and Turkish!</p>
<p>I&rsquo;ve also written <a href="https://pkgdown.r-lib.org/articles/translations.html" target="_blank" rel="noopener"><code>vignette(&quot;translations&quot;)</code></a>
, a brief vignette that discusses how translation works for non-English sites, and includes how you can create translations for new languages. (This is a great way to contribute to pkgdown if you are multi-lingual!)</p>
</li>
</ul>
<h3 id="developer-experience">Developer experience
</h3>
<p>We&rsquo;ve also made a bunch of minor improvements to make improve the package developer experience:</p>
<ul>
<li>
<p>YAML validation has been substantially improved so you should get much clearer errors if you have made a mistake in your <code>_pkgdown.yml</code>. Please <a href="https://github.com/r-lib/pkgdown/issues/new" target="_blank" rel="noopener">file an issue</a>
 if you find a case where the error message is not helpful.</p>
</li>
<li>
<p>The <code>build_*()</code> functions (apart from <a href="https://pkgdown.r-lib.org/reference/build_site.html" target="_blank" rel="noopener"><code>build_site()</code></a>
) no longer automatically preview in interactive sessions since they all emit clickable links to any files that have changed. You can continue to use <a href="https://pkgdown.r-lib.org/reference/preview_site.html" target="_blank" rel="noopener"><code>preview_site()</code></a>
 to open the site in your browser.</p>
</li>
<li>
<p>The <code>build_*()</code> functions now work better if you&rsquo;re previewing just part of a site and haven&rsquo;t built the whole thing. It should no longer be necessary to run <a href="https://pkgdown.r-lib.org/reference/init_site.html" target="_blank" rel="noopener"><code>init_site()</code></a>
 in most cases, and you shouldn&rsquo;t be able to get into a state where you&rsquo;re told to run <a href="https://pkgdown.r-lib.org/reference/init_site.html" target="_blank" rel="noopener"><code>init_site()</code></a>
 and then it doesn&rsquo;t work.</p>
</li>
<li>
<p>We give more and clearer details of the site building process including reporting on exactly what is generated by bslib, what is copied from templates, and what redirects are generated.</p>
</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thanks to all 212 folks who contributed to this release! <a href="https://github.com/Adafede" target="_blank" rel="noopener">@Adafede</a>
, <a href="https://github.com/AEBilgrau" target="_blank" rel="noopener">@AEBilgrau</a>
, <a href="https://github.com/albertocasagrande" target="_blank" rel="noopener">@albertocasagrande</a>
, <a href="https://github.com/alex-d13" target="_blank" rel="noopener">@alex-d13</a>
, <a href="https://github.com/AliSajid" target="_blank" rel="noopener">@AliSajid</a>
, <a href="https://github.com/arkadiuszbeer" target="_blank" rel="noopener">@arkadiuszbeer</a>
, <a href="https://github.com/ArneBab" target="_blank" rel="noopener">@ArneBab</a>
, <a href="https://github.com/asadow" target="_blank" rel="noopener">@asadow</a>
, <a href="https://github.com/ateucher" target="_blank" rel="noopener">@ateucher</a>
, <a href="https://github.com/avhz" target="_blank" rel="noopener">@avhz</a>
, <a href="https://github.com/banfai" target="_blank" rel="noopener">@banfai</a>
, <a href="https://github.com/barcaroli" target="_blank" rel="noopener">@barcaroli</a>
, <a href="https://github.com/BartJanvanRossum" target="_blank" rel="noopener">@BartJanvanRossum</a>
, <a href="https://github.com/bastistician" target="_blank" rel="noopener">@bastistician</a>
, <a href="https://github.com/ben18785" target="_blank" rel="noopener">@ben18785</a>
, <a href="https://github.com/bijoychandraAU" target="_blank" rel="noopener">@bijoychandraAU</a>
, <a href="https://github.com/Bisaloo" target="_blank" rel="noopener">@Bisaloo</a>
, <a href="https://github.com/bkmgit" target="_blank" rel="noopener">@bkmgit</a>
, <a href="https://github.com/bnprks" target="_blank" rel="noopener">@bnprks</a>
, <a href="https://github.com/brycefrank" target="_blank" rel="noopener">@brycefrank</a>
, <a href="https://github.com/bschilder" target="_blank" rel="noopener">@bschilder</a>
, <a href="https://github.com/bundfussr" target="_blank" rel="noopener">@bundfussr</a>
, <a href="https://github.com/cararthompson" target="_blank" rel="noopener">@cararthompson</a>
, <a href="https://github.com/Carol-seven" target="_blank" rel="noopener">@Carol-seven</a>
, <a href="https://github.com/cbailiss" target="_blank" rel="noopener">@cbailiss</a>
, <a href="https://github.com/cboettig" target="_blank" rel="noopener">@cboettig</a>
, <a href="https://github.com/cderv" target="_blank" rel="noopener">@cderv</a>
, <a href="https://github.com/chlebowa" target="_blank" rel="noopener">@chlebowa</a>
, <a href="https://github.com/chuxinyuan" target="_blank" rel="noopener">@chuxinyuan</a>
, <a href="https://github.com/cromanpa94" target="_blank" rel="noopener">@cromanpa94</a>
, <a href="https://github.com/cthombor" target="_blank" rel="noopener">@cthombor</a>
, <a href="https://github.com/d-morrison" target="_blank" rel="noopener">@d-morrison</a>
, <a href="https://github.com/DanChaltiel" target="_blank" rel="noopener">@DanChaltiel</a>
, <a href="https://github.com/DarioS" target="_blank" rel="noopener">@DarioS</a>
, <a href="https://github.com/davidchall" target="_blank" rel="noopener">@davidchall</a>
, <a href="https://github.com/DavisVaughan" target="_blank" rel="noopener">@DavisVaughan</a>
, <a href="https://github.com/dbosak01" target="_blank" rel="noopener">@dbosak01</a>
, <a href="https://github.com/dchiu911" target="_blank" rel="noopener">@dchiu911</a>
, <a href="https://github.com/ddsjoberg" target="_blank" rel="noopener">@ddsjoberg</a>
, <a href="https://github.com/DeepanshKhurana" target="_blank" rel="noopener">@DeepanshKhurana</a>
, <a href="https://github.com/dhersz" target="_blank" rel="noopener">@dhersz</a>
, <a href="https://github.com/dieghernan" target="_blank" rel="noopener">@dieghernan</a>
, <a href="https://github.com/djhocking" target="_blank" rel="noopener">@djhocking</a>
, <a href="https://github.com/dkarletsos" target="_blank" rel="noopener">@dkarletsos</a>
, <a href="https://github.com/dmurdoch" target="_blank" rel="noopener">@dmurdoch</a>
, <a href="https://github.com/dshemetov" target="_blank" rel="noopener">@dshemetov</a>
, <a href="https://github.com/dsweber2" target="_blank" rel="noopener">@dsweber2</a>
, <a href="https://github.com/dvg-p4" target="_blank" rel="noopener">@dvg-p4</a>
, <a href="https://github.com/DyfanJones" target="_blank" rel="noopener">@DyfanJones</a>
, <a href="https://github.com/ecmerkle" target="_blank" rel="noopener">@ecmerkle</a>
, <a href="https://github.com/eddelbuettel" target="_blank" rel="noopener">@eddelbuettel</a>
, <a href="https://github.com/eeholmes" target="_blank" rel="noopener">@eeholmes</a>
, <a href="https://github.com/eitsupi" target="_blank" rel="noopener">@eitsupi</a>
, <a href="https://github.com/eliocamp" target="_blank" rel="noopener">@eliocamp</a>
, <a href="https://github.com/elong0527" target="_blank" rel="noopener">@elong0527</a>
, <a href="https://github.com/EmilHvitfeldt" target="_blank" rel="noopener">@EmilHvitfeldt</a>
, <a href="https://github.com/erikarasnick" target="_blank" rel="noopener">@erikarasnick</a>
, <a href="https://github.com/esimms999" target="_blank" rel="noopener">@esimms999</a>
, <a href="https://github.com/espinielli" target="_blank" rel="noopener">@espinielli</a>
, <a href="https://github.com/etiennebacher" target="_blank" rel="noopener">@etiennebacher</a>
, <a href="https://github.com/ewenharrison" target="_blank" rel="noopener">@ewenharrison</a>
, <a href="https://github.com/filipsch" target="_blank" rel="noopener">@filipsch</a>
, <a href="https://github.com/FlukeAndFeather" target="_blank" rel="noopener">@FlukeAndFeather</a>
, <a href="https://github.com/francoisluc" target="_blank" rel="noopener">@francoisluc</a>
, <a href="https://github.com/friendly" target="_blank" rel="noopener">@friendly</a>
, <a href="https://github.com/fweber144" target="_blank" rel="noopener">@fweber144</a>
, <a href="https://github.com/gaborcsardi" target="_blank" rel="noopener">@gaborcsardi</a>
, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>
, <a href="https://github.com/galachad" target="_blank" rel="noopener">@galachad</a>
, <a href="https://github.com/gangstR" target="_blank" rel="noopener">@gangstR</a>
, <a href="https://github.com/gavinsimpson" target="_blank" rel="noopener">@gavinsimpson</a>
, <a href="https://github.com/GeoBosh" target="_blank" rel="noopener">@GeoBosh</a>
, <a href="https://github.com/GFabien" target="_blank" rel="noopener">@GFabien</a>
, <a href="https://github.com/ggcostoya" target="_blank" rel="noopener">@ggcostoya</a>
, <a href="https://github.com/ghost" target="_blank" rel="noopener">@ghost</a>
, <a href="https://github.com/givison" target="_blank" rel="noopener">@givison</a>
, <a href="https://github.com/gladkia" target="_blank" rel="noopener">@gladkia</a>
, <a href="https://github.com/glin" target="_blank" rel="noopener">@glin</a>
, <a href="https://github.com/gmbecker" target="_blank" rel="noopener">@gmbecker</a>
, <a href="https://github.com/gravesti" target="_blank" rel="noopener">@gravesti</a>
, <a href="https://github.com/GregorDeCillia" target="_blank" rel="noopener">@GregorDeCillia</a>
, <a href="https://github.com/gregorypenn" target="_blank" rel="noopener">@gregorypenn</a>
, <a href="https://github.com/gsmolinski" target="_blank" rel="noopener">@gsmolinski</a>
, <a href="https://github.com/gsrohde" target="_blank" rel="noopener">@gsrohde</a>
, <a href="https://github.com/gungorMetehan" target="_blank" rel="noopener">@gungorMetehan</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/harshkrishna17" target="_blank" rel="noopener">@harshkrishna17</a>
, <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>
, <a href="https://github.com/hfrick" target="_blank" rel="noopener">@hfrick</a>
, <a href="https://github.com/hrecht" target="_blank" rel="noopener">@hrecht</a>
, <a href="https://github.com/hsloot" target="_blank" rel="noopener">@hsloot</a>
, <a href="https://github.com/idavydov" target="_blank" rel="noopener">@idavydov</a>
, <a href="https://github.com/idmn" target="_blank" rel="noopener">@idmn</a>
, <a href="https://github.com/igordot" target="_blank" rel="noopener">@igordot</a>
, <a href="https://github.com/IndrajeetPatil" target="_blank" rel="noopener">@IndrajeetPatil</a>
, <a href="https://github.com/jabenninghoff" target="_blank" rel="noopener">@jabenninghoff</a>
, <a href="https://github.com/jack-davison" target="_blank" rel="noopener">@jack-davison</a>
, <a href="https://github.com/jangorecki" target="_blank" rel="noopener">@jangorecki</a>
, <a href="https://github.com/jayhesselberth" target="_blank" rel="noopener">@jayhesselberth</a>
, <a href="https://github.com/jennybc" target="_blank" rel="noopener">@jennybc</a>
, <a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>
, <a href="https://github.com/JerryWho" target="_blank" rel="noopener">@JerryWho</a>
, <a href="https://github.com/jhelvy" target="_blank" rel="noopener">@jhelvy</a>
, <a href="https://github.com/jmaspons" target="_blank" rel="noopener">@jmaspons</a>
, <a href="https://github.com/john-harrold" target="_blank" rel="noopener">@john-harrold</a>
, <a href="https://github.com/john-ioannides" target="_blank" rel="noopener">@john-ioannides</a>
, <a href="https://github.com/jonasmuench" target="_blank" rel="noopener">@jonasmuench</a>
, <a href="https://github.com/jonnybaik" target="_blank" rel="noopener">@jonnybaik</a>
, <a href="https://github.com/josherrickson" target="_blank" rel="noopener">@josherrickson</a>
, <a href="https://github.com/joshualerickson" target="_blank" rel="noopener">@joshualerickson</a>
, <a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>
, <a href="https://github.com/jplecavalier" target="_blank" rel="noopener">@jplecavalier</a>
, <a href="https://github.com/JSchoenbachler" target="_blank" rel="noopener">@JSchoenbachler</a>
, <a href="https://github.com/juliasilge" target="_blank" rel="noopener">@juliasilge</a>
, <a href="https://github.com/jwimberl" target="_blank" rel="noopener">@jwimberl</a>
, <a href="https://github.com/kalaschnik" target="_blank" rel="noopener">@kalaschnik</a>
, <a href="https://github.com/kevinushey" target="_blank" rel="noopener">@kevinushey</a>
, <a href="https://github.com/klmr" target="_blank" rel="noopener">@klmr</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/LDalby" target="_blank" rel="noopener">@LDalby</a>
, <a href="https://github.com/ldecicco-USGS" target="_blank" rel="noopener">@ldecicco-USGS</a>
, <a href="https://github.com/lhdjung" target="_blank" rel="noopener">@lhdjung</a>
, <a href="https://github.com/LiNk-NY" target="_blank" rel="noopener">@LiNk-NY</a>
, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>
, <a href="https://github.com/Liripo" target="_blank" rel="noopener">@Liripo</a>
, <a href="https://github.com/lorenzwalthert" target="_blank" rel="noopener">@lorenzwalthert</a>
, <a href="https://github.com/lschneiderbauer" target="_blank" rel="noopener">@lschneiderbauer</a>
, <a href="https://github.com/mabesa" target="_blank" rel="noopener">@mabesa</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/maRce10" target="_blank" rel="noopener">@maRce10</a>
, <a href="https://github.com/margotbligh" target="_blank" rel="noopener">@margotbligh</a>
, <a href="https://github.com/marine-ecologist" target="_blank" rel="noopener">@marine-ecologist</a>
, <a href="https://github.com/markfairbanks" target="_blank" rel="noopener">@markfairbanks</a>
, <a href="https://github.com/martinlaw" target="_blank" rel="noopener">@martinlaw</a>
, <a href="https://github.com/matt-dray" target="_blank" rel="noopener">@matt-dray</a>
, <a href="https://github.com/mattfidler" target="_blank" rel="noopener">@mattfidler</a>
, <a href="https://github.com/matthewjnield" target="_blank" rel="noopener">@matthewjnield</a>
, <a href="https://github.com/MattPM" target="_blank" rel="noopener">@MattPM</a>
, <a href="https://github.com/mccarthy-m-g" target="_blank" rel="noopener">@mccarthy-m-g</a>
, <a href="https://github.com/MEO265" target="_blank" rel="noopener">@MEO265</a>
, <a href="https://github.com/merliseclyde" target="_blank" rel="noopener">@merliseclyde</a>
, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>
, <a href="https://github.com/mikeblazanin" target="_blank" rel="noopener">@mikeblazanin</a>
, <a href="https://github.com/mikeroswell" target="_blank" rel="noopener">@mikeroswell</a>
, <a href="https://github.com/mine-cetinkaya-rundel" target="_blank" rel="noopener">@mine-cetinkaya-rundel</a>
, <a href="https://github.com/MLopez-Ibanez" target="_blank" rel="noopener">@MLopez-Ibanez</a>
, <a href="https://github.com/Moohan" target="_blank" rel="noopener">@Moohan</a>
, <a href="https://github.com/mpadge" target="_blank" rel="noopener">@mpadge</a>
, <a href="https://github.com/mrcaseb" target="_blank" rel="noopener">@mrcaseb</a>
, <a href="https://github.com/mrchypark" target="_blank" rel="noopener">@mrchypark</a>
, <a href="https://github.com/ms609" target="_blank" rel="noopener">@ms609</a>
, <a href="https://github.com/msberends" target="_blank" rel="noopener">@msberends</a>
, <a href="https://github.com/musvaage" target="_blank" rel="noopener">@musvaage</a>
, <a href="https://github.com/nanxstats" target="_blank" rel="noopener">@nanxstats</a>
, <a href="https://github.com/nathaneastwood" target="_blank" rel="noopener">@nathaneastwood</a>
, <a href="https://github.com/netique" target="_blank" rel="noopener">@netique</a>
, <a href="https://github.com/nicholascarey" target="_blank" rel="noopener">@nicholascarey</a>
, <a href="https://github.com/nicolerg" target="_blank" rel="noopener">@nicolerg</a>
, <a href="https://github.com/olivroy" target="_blank" rel="noopener">@olivroy</a>
, <a href="https://github.com/pearsonca" target="_blank" rel="noopener">@pearsonca</a>
, <a href="https://github.com/peterdesmet" target="_blank" rel="noopener">@peterdesmet</a>
, <a href="https://github.com/phauchamps" target="_blank" rel="noopener">@phauchamps</a>
, <a href="https://github.com/przmv" target="_blank" rel="noopener">@przmv</a>
, <a href="https://github.com/quantsch" target="_blank" rel="noopener">@quantsch</a>
, <a href="https://github.com/ramiromagno" target="_blank" rel="noopener">@ramiromagno</a>
, <a href="https://github.com/rcannood" target="_blank" rel="noopener">@rcannood</a>
, <a href="https://github.com/rempsyc" target="_blank" rel="noopener">@rempsyc</a>
, <a href="https://github.com/rgaiacs" target="_blank" rel="noopener">@rgaiacs</a>
, <a href="https://github.com/rich-iannone" target="_blank" rel="noopener">@rich-iannone</a>
, <a href="https://github.com/rickhelmus" target="_blank" rel="noopener">@rickhelmus</a>
, <a href="https://github.com/rmflight" target="_blank" rel="noopener">@rmflight</a>
, <a href="https://github.com/robmoss" target="_blank" rel="noopener">@robmoss</a>
, <a href="https://github.com/royfrancis" target="_blank" rel="noopener">@royfrancis</a>
, <a href="https://github.com/rsangole" target="_blank" rel="noopener">@rsangole</a>
, <a href="https://github.com/ryantibs" target="_blank" rel="noopener">@ryantibs</a>
, <a href="https://github.com/salim-b" target="_blank" rel="noopener">@salim-b</a>
, <a href="https://github.com/samuel-marsh" target="_blank" rel="noopener">@samuel-marsh</a>
, <a href="https://github.com/SebKrantz" target="_blank" rel="noopener">@SebKrantz</a>
, <a href="https://github.com/SESjo" target="_blank" rel="noopener">@SESjo</a>
, <a href="https://github.com/sgvignali" target="_blank" rel="noopener">@sgvignali</a>
, <a href="https://github.com/spsanderson" target="_blank" rel="noopener">@spsanderson</a>
, <a href="https://github.com/srfall" target="_blank" rel="noopener">@srfall</a>
, <a href="https://github.com/stefanoborini" target="_blank" rel="noopener">@stefanoborini</a>
, <a href="https://github.com/stephenashton-dhsc" target="_blank" rel="noopener">@stephenashton-dhsc</a>
, <a href="https://github.com/strengejacke" target="_blank" rel="noopener">@strengejacke</a>
, <a href="https://github.com/swsoyee" target="_blank" rel="noopener">@swsoyee</a>
, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>
, <a href="https://github.com/talgalili" target="_blank" rel="noopener">@talgalili</a>
, <a href="https://github.com/tanho63" target="_blank" rel="noopener">@tanho63</a>
, <a href="https://github.com/tedmoorman" target="_blank" rel="noopener">@tedmoorman</a>
, <a href="https://github.com/telphick" target="_blank" rel="noopener">@telphick</a>
, <a href="https://github.com/TFKentUSDA" target="_blank" rel="noopener">@TFKentUSDA</a>
, <a href="https://github.com/ThierryO" target="_blank" rel="noopener">@ThierryO</a>
, <a href="https://github.com/thisisnic" target="_blank" rel="noopener">@thisisnic</a>
, <a href="https://github.com/thomasp85" target="_blank" rel="noopener">@thomasp85</a>
, <a href="https://github.com/tomsing1" target="_blank" rel="noopener">@tomsing1</a>
, <a href="https://github.com/tony-aw" target="_blank" rel="noopener">@tony-aw</a>
, <a href="https://github.com/trevorld" target="_blank" rel="noopener">@trevorld</a>
, <a href="https://github.com/tylerlittlefield" target="_blank" rel="noopener">@tylerlittlefield</a>
, <a href="https://github.com/uriahf" target="_blank" rel="noopener">@uriahf</a>
, <a href="https://github.com/urswilke" target="_blank" rel="noopener">@urswilke</a>
, <a href="https://github.com/ValValetl" target="_blank" rel="noopener">@ValValetl</a>
, <a href="https://github.com/venpopov" target="_blank" rel="noopener">@venpopov</a>
, <a href="https://github.com/vincentvanhees" target="_blank" rel="noopener">@vincentvanhees</a>
, <a href="https://github.com/wangq13" target="_blank" rel="noopener">@wangq13</a>
, <a href="https://github.com/willgearty" target="_blank" rel="noopener">@willgearty</a>
, <a href="https://github.com/wviechtb" target="_blank" rel="noopener">@wviechtb</a>
, <a href="https://github.com/xuyiqing" target="_blank" rel="noopener">@xuyiqing</a>
, <a href="https://github.com/yjunechoe" target="_blank" rel="noopener">@yjunechoe</a>
, <a href="https://github.com/ynsec37" target="_blank" rel="noopener">@ynsec37</a>
, <a href="https://github.com/zeehio" target="_blank" rel="noopener">@zeehio</a>
, and <a href="https://github.com/zkamvar" target="_blank" rel="noopener">@zkamvar</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2024/pkgdown-2-1-0/thumbnail-wd.jpg" length="68610" type="image/jpeg" />
    </item>
    <item>
      <title>withr 3.0.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2024/withr-3-0-0/</link>
      <pubDate>Thu, 18 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2024/withr-3-0-0/</guid>
      <dc:creator>Lionel Henry</dc:creator><description><![CDATA[<p>It&rsquo;s not without jubilant bearing that we announce the release of the 3.0.0 version of <a href="https://withr.r-lib.org/" target="_blank" rel="noopener">withr</a>
, the tidyverse solution for automatic cleanup of resources! In this release, the internals of withr were rewritten to improve the performance and increase the compatibility with base R&rsquo;s <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 mechanism.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"withr"</span><span class='o'>)</span></span></code></pre>
</div>
<p>In this blog post we&rsquo;ll go over the changes that made this rewrite possible, but first we&rsquo;ll review the cleanup strategies made possible by withr.</p>
<p>You can see a full list of changes in the <a href="https://withr.r-lib.org/news/index.html#withr-300" target="_blank" rel="noopener">release notes</a>
.</p>
<div class="highlight">
</div>
<h2 id="cleaning-up-resources-with-base-r-and-with-withr">Cleaning up resources with base R and with withr
</h2>
<p>Traditionally, resource cleanup in R is done with <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>base::on.exit()</code></a>
. Cleaning up in the on-exit hook ensures that the cleanup happens both in the normal case, when the code has finished running without error, and in the error case, when something went wrong and execution is interrupted.</p>
<p><a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 is meant to be used inside functions but it also works within <a href="https://rdrr.io/r/base/eval.html" target="_blank" rel="noopener"><code>local()</code></a>
, which we&rsquo;ll use here for our examples:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/eval.html'>local</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/on.exit.html'>on.exit</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/message.html'>message</a></span><span class='o'>(</span><span class='s'>"Cleaning time!"</span><span class='o'>)</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>1</span> <span class='o'>+</span> <span class='m'>2</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 3</span></span>
<span></span><span><span class='c'>#&gt; Cleaning time!</span></span>
<span></span></code></pre>
</div>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/eval.html'>local</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/on.exit.html'>on.exit</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/message.html'>message</a></span><span class='o'>(</span><span class='s'>"Cleaning time!"</span><span class='o'>)</span><span class='o'>)</span></span>
<span>  <span class='kr'><a href='https://rdrr.io/r/base/stop.html'>stop</a></span><span class='o'>(</span><span class='s'>"uh oh"</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>1</span> <span class='o'>+</span> <span class='m'>2</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> uh oh</span></span>
<span></span><span><span class='c'>#&gt; Cleaning time!</span></span>
<span></span></code></pre>
</div>
<p><a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 is guaranteed to run no matter what and this property makes it invaluable for resource cleaning. No more accidental littering!</p>
<p>However the process of cleaning up this way can be a bit verbose and feel too manual. Here is how you&rsquo;d create and clean up a temporary file for instance:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/eval.html'>local</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nv'>my_file</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/tempfile.html'>tempfile</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/files.html'>file.create</a></span><span class='o'>(</span><span class='nv'>my_file</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/on.exit.html'>on.exit</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/files.html'>file.remove</a></span><span class='o'>(</span><span class='nv'>my_file</span><span class='o'>)</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/writeLines.html'>writeLines</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"a"</span>, <span class='s'>"b"</span><span class='o'>)</span>, con <span class='o'>=</span> <span class='nv'>my_file</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>Wouldn&rsquo;t it be great if we could wrap this code up in a function? That&rsquo;s the goal of withr&rsquo;s <code>local_</code>-prefixed functions. They combine both the creation or modification of a resource and its (eventual) restoration to the original state into a single function:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/eval.html'>local</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nv'>my_file</span> <span class='o'>&lt;-</span> <span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/with_tempfile.html'>local_tempfile</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/writeLines.html'>writeLines</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"a"</span>, <span class='s'>"b"</span><span class='o'>)</span>, con <span class='o'>=</span> <span class='nv'>my_file</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>In this case we have created a resource (a file), but the same principle applies to modifying resources such as global options:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/eval.html'>local</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='c'># Let's temporarily print with a single decimal place</span></span>
<span>  <span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/with_options.html'>local_options</a></span><span class='o'>(</span>digits <span class='o'>=</span> <span class='m'>1</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>/</span><span class='m'>3</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 0.3</span></span>
<span></span><span></span>
<span><span class='c'># The original option value has been restored</span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/options.html'>getOption</a></span><span class='o'>(</span><span class='s'>"digits"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 7</span></span>
<span></span><span></span>
<span><span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>/</span><span class='m'>3</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 0.3333333</span></span>
<span></span></code></pre>
</div>
<p>And you can equivalently use the <code>with_</code>-prefixed variants (from which the package takes its name!), this way you don&rsquo;t need to wrap in <a href="https://rdrr.io/r/base/eval.html" target="_blank" rel="noopener"><code>local()</code></a>
:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/with_options.html'>with_options</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span>digits <span class='o'>=</span> <span class='m'>1</span><span class='o'>)</span>, <span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>/</span><span class='m'>3</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 0.3</span></span>
<span></span></code></pre>
</div>
<p>The <code>with_</code> functions are useful for creating very small scopes for given resources, inside or outside a function.</p>
<h2 id="the-withr-300-rewrite">The withr 3.0.0 rewrite
</h2>
<p>Traditionally, withr implemented its own exit event system on top of <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
. We needed an extra layer because of a couple of missing features:</p>
<ul>
<li>
<p>When multiple resources are managed by a piece of code, the order in which these resources are restored or cleaned up sometimes matter. The most consistent order for cleanup is last-in first-out (LIFO). In other words the oldest resource, on which younger resources might depend, is cleaned up last. But historically R only supported first-in first-out (FIFO) order.</p>
</li>
<li>
<p>The other missing piece was being able to inspect the contents of the exit hook. The <a href="https://rdrr.io/r/base/sys.parent.html" target="_blank" rel="noopener"><code>sys.on.exit()</code></a>
 R helper was created for this purpose but was affected by a bug that prevented it from working inside functions.</p>
</li>
</ul>
<p>We contributed two changes to R 3.5.0 that filled these missing pieces, fixing the <a href="https://rdrr.io/r/base/sys.parent.html" target="_blank" rel="noopener"><code>sys.on.exit()</code></a>
 bug and adding an <code>after</code> argument to <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 to allow last-in first-out ordering.</p>
<p>Until now, we haven&rsquo;t been able to leverage these contributions because of our policy of <a href="https://www.tidyverse.org/blog/2019/04/r-version-support" target="_blank" rel="noopener">supporting the current and previous four versions of R</a>
. Now that enough time has passed, it was time for a rewrite! Our version of <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>base::on.exit()</code></a>
 is <a href="https://withr.r-lib.org/reference/defer.html" target="_blank" rel="noopener"><code>withr::defer()</code></a>
. Along with better default behaviour, <a href="https://withr.r-lib.org/reference/defer.html" target="_blank" rel="noopener"><code>withr::defer()</code></a>
 allows the clean up of resources non-locally (ironically an essential feature for implementing <code>local_</code> functions). Given the changes in R 3.5.0, <a href="https://withr.r-lib.org/reference/defer.html" target="_blank" rel="noopener"><code>withr::defer()</code></a>
 can now be implemented as a simple wrapper around <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
.</p>
<p>One benefit of the rewrite is that mixing withr tools and <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 in the same function now correctly interleaves cleanup:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/eval.html'>local</a></span><span class='o'>(</span><span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/on.exit.html'>on.exit</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>1</span><span class='o'>)</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/defer.html'>defer</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>2</span><span class='o'>)</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/on.exit.html'>on.exit</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>3</span><span class='o'>)</span>, add <span class='o'>=</span> <span class='kc'>TRUE</span>, after <span class='o'>=</span> <span class='kc'>FALSE</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/defer.html'>defer</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>4</span><span class='o'>)</span><span class='o'>)</span></span>
<span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/print.html'>print</a></span><span class='o'>(</span><span class='m'>5</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] 5</span></span>
<span><span class='c'>#&gt; [1] 4</span></span>
<span><span class='c'>#&gt; [1] 3</span></span>
<span><span class='c'>#&gt; [1] 2</span></span>
<span><span class='c'>#&gt; [1] 1</span></span>
<span></span></code></pre>
</div>
<p>But the main benefit is increased performance. Here is how <code>defer()</code> compared to <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 in the previous version:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>base</span> <span class='o'>&lt;-</span> <span class='kr'>function</span><span class='o'>(</span><span class='o'>)</span> <span class='nf'><a href='https://rdrr.io/r/base/on.exit.html'>on.exit</a></span><span class='o'>(</span><span class='kc'>NULL</span><span class='o'>)</span></span>
<span><span class='nv'>withr</span> <span class='o'>&lt;-</span> <span class='kr'>function</span><span class='o'>(</span><span class='o'>)</span> <span class='nf'>defer</span><span class='o'>(</span><span class='kc'>NULL</span><span class='o'>)</span></span>
<span></span>
<span><span class='c'># withr 2.5.2</span></span>
<span><span class='nf'>bench</span><span class='nf'>::</span><span class='nf'><a href='http://bench.r-lib.org/reference/mark.html'>mark</a></span><span class='o'>(</span><span class='nf'>base</span><span class='o'>(</span><span class='o'>)</span>, <span class='nf'>withr</span><span class='o'>(</span><span class='o'>)</span>, check <span class='o'>=</span> <span class='kc'>FALSE</span><span class='o'>)</span><span class='o'>[</span><span class='m'>1</span><span class='o'>:</span><span class='m'>8</span><span class='o'>]</span></span>
<span><span class='c'>#&gt; # A tibble: 2 × 8</span></span>
<span><span class='c'>#&gt;   expression      min median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc</span></span>
<span><span class='c'>#&gt;   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:&gt;     &lt;dbl&gt; &lt;bch:byt&gt;    &lt;dbl&gt; &lt;int&gt; &lt;dbl&gt;</span></span>
<span><span class='c'>#&gt; 1 base()            0   82ns  6954952.        0B    696.   9999     1</span></span>
<span><span class='c'>#&gt; 2 withr()      26.2µs 27.9µs    35172.    88.4KB     52.8  9985    15</span></span></code></pre>
</div>
<p>withr 3.0.0 has now caught up to <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 quite a bit:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># withr 3.0.0</span></span>
<span><span class='nf'>bench</span><span class='nf'>::</span><span class='nf'><a href='http://bench.r-lib.org/reference/mark.html'>mark</a></span><span class='o'>(</span><span class='nf'>base</span><span class='o'>(</span><span class='o'>)</span>, <span class='nf'>withr</span><span class='o'>(</span><span class='o'>)</span>, check <span class='o'>=</span> <span class='kc'>FALSE</span><span class='o'>)</span><span class='o'>[</span><span class='m'>1</span><span class='o'>:</span><span class='m'>8</span><span class='o'>]</span></span>
<span><span class='c'>#&gt; # A tibble: 2 × 8</span></span>
<span><span class='c'>#&gt;   expression      min median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc</span></span>
<span><span class='c'>#&gt;   &lt;bch:expr&gt; &lt;bch:tm&gt; &lt;bch:&gt;     &lt;dbl&gt; &lt;bch:byt&gt;    &lt;dbl&gt; &lt;int&gt; &lt;dbl&gt;</span></span>
<span><span class='c'>#&gt; 1 base()            0   82ns  7329829.        0B       0  10000     0</span></span>
<span><span class='c'>#&gt; 2 withr()      2.95µs  3.4µs   280858.        0B     225.  9992     8</span></span></code></pre>
</div>
<p>Of course <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 is still much faster, in part because <code>defer()</code> supports more features (more on that below), but mostly because <code>on.exit</code> is a primitive function whereas <code>defer()</code> is implemented as a normal R function. That said, we hope that we now have made <code>defer()</code> (and the <code>local_</code> and <code>with_</code> functions that use it) sufficiently fast to be used even in performance-critical micro-tools.</p>
<h2 id="improved-withr-features">Improved withr features
</h2>
<p>Over the successive releases of withr we&rsquo;ve improved the behaviour of cleanup expressions interactively, in scripts executed with <a href="https://rdrr.io/r/base/source.html" target="_blank" rel="noopener"><code>source()</code></a>
, and in knitr. <a href="https://rdrr.io/r/base/on.exit.html" target="_blank" rel="noopener"><code>on.exit()</code></a>
 is a bit inconsistent when it is used outside of a function:</p>
<ul>
<li>Interactively, it doesn&rsquo;t do anything.</li>
<li>In <a href="https://rdrr.io/r/base/source.html" target="_blank" rel="noopener"><code>source()</code></a>
 and in knitr, it runs immediately instead of a the end of the script</li>
</ul>
<p><a href="https://withr.r-lib.org/reference/defer.html" target="_blank" rel="noopener"><code>withr::defer()</code></a>
 and the <a href="https://withr.r-lib.org/reference/with_.html" target="_blank" rel="noopener"><code>withr::local_</code></a>
 helpers try to be more helpful for these cases.</p>
<p>Interactively, it saves the cleanup action in a special global hook and you get information about how to actually perform the cleanup:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>file</span> <span class='o'>&lt;-</span> <span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/with_tempfile.html'>local_tempfile</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Setting global deferred event(s).</span></span>
<span><span class='c'>#&gt; i These will be run:</span></span>
<span><span class='c'>#&gt;   * Automatically, when the R session ends.</span></span>
<span><span class='c'>#&gt;   * On demand, if you call `withr::deferred_run()`.</span></span>
<span><span class='c'>#&gt; i Use `withr::deferred_clear()` to clear them without executing.</span></span>
<span></span>
<span><span class='c'># Clean up now</span></span>
<span><span class='nf'>withr</span><span class='nf'>::</span><span class='nf'><a href='https://withr.r-lib.org/reference/defer.html'>deferred_run</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Ran 1/1 deferred expressions</span></span></code></pre>
</div>
<p>In knitr or <a href="https://rdrr.io/r/base/source.html" target="_blank" rel="noopener"><code>source()</code></a>
<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, the cleanup is performed at the end of the document or of the script. If you need chunk-level cleanup, use <a href="https://rdrr.io/r/base/eval.html" target="_blank" rel="noopener"><code>local()</code></a>
 as we&rsquo;ve been doing in the examples of this blog post:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">Cleaning up at the end of the document:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s">```r
</span></span></span><span class="line"><span class="cl"><span class="n">document_wide_file</span> <span class="o">&lt;-</span> <span class="n">withr</span><span class="o">::</span><span class="nf">local_tempfile</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="s">```</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cleaning up at the end of the chunk:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="s">```r
</span></span></span><span class="line"><span class="cl"><span class="nf">local</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="n">local_file</span> <span class="o">&lt;-</span> <span class="n">withr</span><span class="o">::</span><span class="nf">local_tempfile</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="s">```</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Starting from withr 3.0.0, you can also run <code>deferred_run()</code> inside of a chunk:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="s">```r
</span></span></span><span class="line"><span class="cl"><span class="n">withr</span><span class="o">::</span><span class="nf">deferred_run</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Ran 1/1 deferred expressions</span>
</span></span><span class="line"><span class="cl"><span class="s">```</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="acknowledgements">Acknowledgements
</h2>
<p>Thanks to the github contributors who helped us with this release!</p>
<p><a href="https://github.com/ashbythorpe" target="_blank" rel="noopener">@ashbythorpe</a>
, <a href="https://github.com/bastistician" target="_blank" rel="noopener">@bastistician</a>
, <a href="https://github.com/DavisVaughan" target="_blank" rel="noopener">@DavisVaughan</a>
, <a href="https://github.com/fkohrt" target="_blank" rel="noopener">@fkohrt</a>
, <a href="https://github.com/gaborcsardi" target="_blank" rel="noopener">@gaborcsardi</a>
, <a href="https://github.com/gdurif" target="_blank" rel="noopener">@gdurif</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>
, <a href="https://github.com/honghaoli42" target="_blank" rel="noopener">@honghaoli42</a>
, <a href="https://github.com/IndrajeetPatil" target="_blank" rel="noopener">@IndrajeetPatil</a>
, <a href="https://github.com/jameslairdsmith" target="_blank" rel="noopener">@jameslairdsmith</a>
, <a href="https://github.com/jennybc" target="_blank" rel="noopener">@jennybc</a>
, <a href="https://github.com/jonkeane" target="_blank" rel="noopener">@jonkeane</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>
, <a href="https://github.com/MLopez-Ibanez" target="_blank" rel="noopener">@MLopez-Ibanez</a>
, <a href="https://github.com/moodymudskipper" target="_blank" rel="noopener">@moodymudskipper</a>
, <a href="https://github.com/multimeric" target="_blank" rel="noopener">@multimeric</a>
, <a href="https://github.com/orichters" target="_blank" rel="noopener">@orichters</a>
, <a href="https://github.com/pfuehrlich-pik" target="_blank" rel="noopener">@pfuehrlich-pik</a>
, <a href="https://github.com/solmos" target="_blank" rel="noopener">@solmos</a>
, <a href="https://github.com/tillea" target="_blank" rel="noopener">@tillea</a>
, and <a href="https://github.com/vanhry" target="_blank" rel="noopener">@vanhry</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://rdrr.io/r/base/source.html" target="_blank" rel="noopener"><code>source()</code></a>
 is only supported by default when running in the global environment, which is usually the case. For the special case of sourcing in a local environment, you need to set <code>options(withr.hook_source = TRUE)</code> first.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2024/withr-3-0-0/thumbnail-wd.jpg" length="251649" type="image/jpeg" />
    </item>
    <item>
      <title>roxygen2 7.3.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2024/roxygen2-7-3-0/</link>
      <pubDate>Thu, 11 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2024/roxygen2-7-3-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re well pleased to announce the release of <a href="http://roxygen2.r-lib.org/" target="_blank" rel="noopener">roxygen2</a>
 7.3.0. roxygen2 allows you to write specially formatted R comments that generate R documentation files (<code>man/*.Rd</code>) and the <code>NAMESPACE</code> file. roxygen2 is used by over 13,000 CRAN packages.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"roxygen2"</span><span class='o'>)</span></span></code></pre>
</div>
<p>There are four major improvements in this release:</p>
<ul>
<li>
<p>The <code>NAMESPACE</code> roclet now reports if you have S3 methods that are missing an <code>@export</code> tag. All S3 methods need to be <code>@export</code>ed even if the generic is not. This avoids rare, but hard to debug, problems. If you think this is giving a false positive, <a href="https://github.com/r-lib/roxygen2/issues/new" target="_blank" rel="noopener">please file an issue</a>
 and suppress the warning with <code>@exportS3Method NULL</code>.</p>
<p>I&rsquo;ve also considerably revamped the documentation for S3 methods in <a href="https://roxygen2.r-lib.org/dev/articles/namespace.html#s3" target="_blank" rel="noopener"><code>vignette(&quot;namespace&quot;)</code></a>
. The docs now discuss what exporting an S3 method really means, and why it would be technically better to call it <em>registering</em> the method.</p>
</li>
<li>
<p>Finally, the <code>NAMESPACE</code> roclet once again regenerates imports <em>before</em> loading package code and parsing roxygen blocks. This has been the goal for a <a href="https://github.com/r-lib/roxygen2/issues/372" target="_blank" rel="noopener">long time</a>
, but we accidentally broke it when adding support for code execution in markdown blocks. This change resolves a family of problems where you somehow bork your <code>NAMESPACE</code> and can&rsquo;t easily get fix it because you can&rsquo;t re-document the package because you can&rsquo;t load your package because your <code>NAMESPACE</code> is borked.</p>
</li>
<li>
<p><code>@docType package</code> now works like <a href="https://roxygen2.r-lib.org/articles/rd-other.html#packages" target="_blank" rel="noopener"><code>&quot;_PACKAGE&quot;</code></a>
, including creating a <code>{packagename}-package</code> alias automatically. This resolves a bug introduced in roxygen2 7.0.0 that meant that many packages lacked the correct alias for their package documentation topic.</p>
</li>
<li>
<p><code>&quot;_PACKAGE&quot;</code> does a better job of automatically generating aliases. In particular, it will no longer generate a duplicate alias if you have a function with the same name as your package (like <a href="https://glue.tidyverse.org/reference/glue.html" target="_blank" rel="noopener"><code>glue::glue()</code></a>
 or <a href="https://reprex.tidyverse.org/reference/reprex.html" target="_blank" rel="noopener"><code>reprex::reprex()</code></a>
). If you&rsquo;ve previously had to hack around this bug, you can now delete any custom <code>@aliases</code> tags associated with the <code>&quot;_PACKAGE&quot;</code> docs.</p>
</li>
</ul>
<p>You can see a full list of other minor improvements and bug fixes in the <a href="https://github.com/r-lib/roxygen2/releases/tag/v7.3.0" target="_blank" rel="noopener">release notes</a>
.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thanks to the 46 folks who helped make this release possible through their thoughtful questions and carefully crafted code! <a href="https://github.com/andrewmarx" target="_blank" rel="noopener">@andrewmarx</a>
, <a href="https://github.com/ashbythorpe" target="_blank" rel="noopener">@ashbythorpe</a>
, <a href="https://github.com/ateucher" target="_blank" rel="noopener">@ateucher</a>
, <a href="https://github.com/bahadzie" target="_blank" rel="noopener">@bahadzie</a>
, <a href="https://github.com/bastistician" target="_blank" rel="noopener">@bastistician</a>
, <a href="https://github.com/beginb" target="_blank" rel="noopener">@beginb</a>
, <a href="https://github.com/brodieG" target="_blank" rel="noopener">@brodieG</a>
, <a href="https://github.com/bryanhanson" target="_blank" rel="noopener">@bryanhanson</a>
, <a href="https://github.com/cbielow" target="_blank" rel="noopener">@cbielow</a>
, <a href="https://github.com/daattali" target="_blank" rel="noopener">@daattali</a>
, <a href="https://github.com/DanChaltiel" target="_blank" rel="noopener">@DanChaltiel</a>
, <a href="https://github.com/dpprdan" target="_blank" rel="noopener">@dpprdan</a>
, <a href="https://github.com/dsweber2" target="_blank" rel="noopener">@dsweber2</a>
, <a href="https://github.com/espinielli" target="_blank" rel="noopener">@espinielli</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/hughjonesd" target="_blank" rel="noopener">@hughjonesd</a>
, <a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>
, <a href="https://github.com/jmbarbone" target="_blank" rel="noopener">@jmbarbone</a>
, <a href="https://github.com/johnbaums" target="_blank" rel="noopener">@johnbaums</a>
, <a href="https://github.com/jonocarroll" target="_blank" rel="noopener">@jonocarroll</a>
, <a href="https://github.com/kathi-munk" target="_blank" rel="noopener">@kathi-munk</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/kylebutts" target="_blank" rel="noopener">@kylebutts</a>
, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>
, <a href="https://github.com/LouisLeNezet" target="_blank" rel="noopener">@LouisLeNezet</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/MaximilianPi" target="_blank" rel="noopener">@MaximilianPi</a>
, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>
, <a href="https://github.com/moodymudskipper" target="_blank" rel="noopener">@moodymudskipper</a>
, <a href="https://github.com/msberends" target="_blank" rel="noopener">@msberends</a>
, <a href="https://github.com/multimeric" target="_blank" rel="noopener">@multimeric</a>
, <a href="https://github.com/musvaage" target="_blank" rel="noopener">@musvaage</a>
, <a href="https://github.com/neshvig10" target="_blank" rel="noopener">@neshvig10</a>
, <a href="https://github.com/olivroy" target="_blank" rel="noopener">@olivroy</a>
, <a href="https://github.com/ralmond" target="_blank" rel="noopener">@ralmond</a>
, <a href="https://github.com/RMHogervorst" target="_blank" rel="noopener">@RMHogervorst</a>
, <a href="https://github.com/Robinlovelace" target="_blank" rel="noopener">@Robinlovelace</a>
, <a href="https://github.com/rossellhayes" target="_blank" rel="noopener">@rossellhayes</a>
, <a href="https://github.com/rsbivand" target="_blank" rel="noopener">@rsbivand</a>
, <a href="https://github.com/sbgraves237" target="_blank" rel="noopener">@sbgraves237</a>
, <a href="https://github.com/schradj" target="_blank" rel="noopener">@schradj</a>
, <a href="https://github.com/sebffischer" target="_blank" rel="noopener">@sebffischer</a>
, <a href="https://github.com/simonpcouch" target="_blank" rel="noopener">@simonpcouch</a>
, <a href="https://github.com/stemangiola" target="_blank" rel="noopener">@stemangiola</a>
, <a href="https://github.com/tau31" target="_blank" rel="noopener">@tau31</a>
, and <a href="https://github.com/trusch139" target="_blank" rel="noopener">@trusch139</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2024/roxygen2-7-3-0/thumbnail-wd.jpg" length="161750" type="image/jpeg" />
    </item>
    <item>
      <title>Three ways errors are about to get better in tidymodels</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2023/tidymodels-errors-q4/</link>
      <pubDate>Fri, 10 Nov 2023 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2023/tidymodels-errors-q4/</guid>
      <dc:creator>Simon Couch</dc:creator><description><![CDATA[<p>Twice a year, the tidymodels team comes together for &ldquo;spring cleaning,&rdquo; a week-long project devoted to package maintenance. Ahead of the week, we come up with a list of maintenance tasks that we&rsquo;d like to see consistently implemented across our packages. Many of these tasks can be completed by running one usethis function, while others are much more involved, like issue triage.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> In tidymodels, triaging issues in our core packages helps us to better understand common ways that users struggle to wrap their heads around an API choice we&rsquo;ve made or find the information they need. So, among other things, refinements to the wording of our error messages is a common output of our spring cleanings. This blog post will call out three kinds of changes to our erroring that came out of this spring cleaning:</p>
<ul>
<li>Improving existing errors: <a href="#outcome">The outcome went missing</a>
</li>
<li>Do something where we once did nothing: <a href="#predict">Predicting with things that can&rsquo;t predict</a>
</li>
<li>Make a place and point to it: <a href="#model">Model formulas</a>
</li>
</ul>
<p>To demonstrate, we&rsquo;ll walk through some examples using the tidymodels packages:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://tidymodels.tidymodels.org'>tidymodels</a></span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ── <span style='font-weight: bold;'>Attaching packages</span> ──────────────────────────── tidymodels 1.1.1 ──</span></span>
<span></span><span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>broom       </span> 1.0.5          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>recipes     </span> 1.0.8.<span style='color: #BB0000;'>9000</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>dials       </span> 1.2.0          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>rsample     </span> 1.2.0     </span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>dplyr       </span> 1.1.3          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>tibble      </span> 3.2.1     </span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>ggplot2     </span> 3.4.4          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>tidyr       </span> 1.3.0     </span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>infer       </span> 1.0.5          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>tune        </span> 1.1.2.<span style='color: #BB0000;'>9000</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>modeldata   </span> 1.2.0          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>workflows   </span> 1.1.3     </span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>parsnip     </span> 1.1.1.<span style='color: #BB0000;'>9001</span>     <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>workflowsets</span> 1.0.1     </span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>purrr       </span> 1.0.2          <span style='color: #00BB00;'>✔</span> <span style='color: #0000BB;'>yardstick   </span> 1.2.0</span></span>
<span></span><span><span class='c'>#&gt; ── <span style='font-weight: bold;'>Conflicts</span> ─────────────────────────────── tidymodels_conflicts() ──</span></span>
<span><span class='c'>#&gt; <span style='color: #BB0000;'>✖</span> <span style='color: #0000BB;'>purrr</span>::<span style='color: #00BB00;'>discard()</span> masks <span style='color: #0000BB;'>scales</span>::discard()</span></span>
<span><span class='c'>#&gt; <span style='color: #BB0000;'>✖</span> <span style='color: #0000BB;'>dplyr</span>::<span style='color: #00BB00;'>filter()</span>  masks <span style='color: #0000BB;'>stats</span>::filter()</span></span>
<span><span class='c'>#&gt; <span style='color: #BB0000;'>✖</span> <span style='color: #0000BB;'>dplyr</span>::<span style='color: #00BB00;'>lag()</span>     masks <span style='color: #0000BB;'>stats</span>::lag()</span></span>
<span><span class='c'>#&gt; <span style='color: #BB0000;'>✖</span> <span style='color: #0000BB;'>recipes</span>::<span style='color: #00BB00;'>step()</span>  masks <span style='color: #0000BB;'>stats</span>::step()</span></span>
<span><span class='c'>#&gt; <span style='color: #0000BB;'>•</span> Use suppressPackageStartupMessages() to eliminate package startup messages</span></span>
<span></span></code></pre>
</div>
<p>Note that my installed versions include the current dev version of a few tidymodels packages. You can install those versions with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='https://pak.r-lib.org/reference/pak.html'>pak</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/paste.html'>paste0</a></span><span class='o'>(</span><span class='s'>"tidymodels/"</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"tune"</span>, <span class='s'>"parsnip"</span>, <span class='s'>"recipes"</span><span class='o'>)</span><span class='o'>)</span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="the-outcome-went-missing-">The outcome went missing 👻
</h2>
<p>The tidymodels packages focus on <em>supervised</em> machine learning problems, predicting the value of an outcome using predictors.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> For example, in the code:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>linear_spec</span> <span class='o'>&lt;-</span> <span class='nf'>linear_reg</span><span class='o'>(</span><span class='o'>)</span></span>
<span></span>
<span><span class='nv'>linear_fit</span> <span class='o'>&lt;-</span> <span class='nf'>fit</span><span class='o'>(</span><span class='nv'>linear_spec</span>, <span class='nv'>mpg</span> <span class='o'>~</span> <span class='nv'>hp</span>, <span class='nv'>mtcars</span><span class='o'>)</span></span></code></pre>
</div>
<p>The <code>mpg</code> variable is the outcome. There are many ways that an analyst may mistakenly fail to pass an outcome. In the most straightforward case, they might omit the outcome on the LHS of the formula:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">fit</span><span class="p">(</span><span class="n">linear_spec</span><span class="p">,</span> <span class="o">~</span> <span class="n">hp</span><span class="p">,</span> <span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error in lm.fit(x, y, offset = offset, singular.ok = singular.ok, ...) : </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   incompatible dimensions</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In this case, parsnip used to defer to the modeling engine to raise an error, which may or may not be informative.</p>
<p>There are many less obvious ways an analyst may mistakenly supply no outcome variable. For example, try spotting the issue in the following code, defining a recipe to perform principal component analysis (PCA) on the numeric variables in the data before fitting the model:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">mtcars_rec</span> <span class="o">&lt;-</span>
</span></span><span class="line"><span class="cl">  <span class="nf">recipe</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">.,</span> <span class="n">mtcars</span><span class="p">)</span> <span class="o">%&gt;%</span>
</span></span><span class="line"><span class="cl">  <span class="nf">step_pca</span><span class="p">(</span><span class="nf">all_numeric</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">workflow</span><span class="p">(</span><span class="n">mtcars_rec</span><span class="p">,</span> <span class="n">linear_spec</span><span class="p">)</span> <span class="o">%&gt;%</span> <span class="nf">fit</span><span class="p">(</span><span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error: object &#39;.&#39; not found</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>A head-scratcher! To help diagnose what&rsquo;s happening here, we could first try seeing what data is actually being passed to the model.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>mtcars_rec_trained</span> <span class='o'>&lt;-</span></span>
<span>  <span class='nv'>mtcars_rec</span> <span class='o'>%&gt;%</span> </span>
<span>  <span class='nf'>prep</span><span class='o'>(</span><span class='nv'>mtcars</span><span class='o'>)</span> </span>
<span></span>
<span><span class='nv'>mtcars_rec_trained</span> <span class='o'>%&gt;%</span> <span class='nf'>bake</span><span class='o'>(</span><span class='kc'>NULL</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 32 × 5</span></span></span>
<span><span class='c'>#&gt;      PC1   PC2    PC3     PC4    PC5</span></span>
<span><span class='c'>#&gt;    <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span> <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>   <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span>  <span style='color: #555555; font-style: italic;'>&lt;dbl&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 1</span> -<span style='color: #BB0000;'>195.</span>  12.8 -<span style='color: #BB0000;'>11.4</span>   0.016<span style='text-decoration: underline;'>4</span>  2.17 </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 2</span> -<span style='color: #BB0000;'>195.</span>  12.9 -<span style='color: #BB0000;'>11.7</span>  -<span style='color: #BB0000;'>0.479</span>   2.11 </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 3</span> -<span style='color: #BB0000;'>142.</span>  25.9 -<span style='color: #BB0000;'>16.0</span>  -<span style='color: #BB0000;'>1.34</span>   -<span style='color: #BB0000;'>1.18</span> </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 4</span> -<span style='color: #BB0000;'>279.</span> -<span style='color: #BB0000;'>38.3</span> -<span style='color: #BB0000;'>14.0</span>   0.157  -<span style='color: #BB0000;'>0.817</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 5</span> -<span style='color: #BB0000;'>399.</span> -<span style='color: #BB0000;'>37.3</span>  -<span style='color: #BB0000;'>1.38</span>  2.56   -<span style='color: #BB0000;'>0.444</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 6</span> -<span style='color: #BB0000;'>248.</span> -<span style='color: #BB0000;'>25.6</span> -<span style='color: #BB0000;'>12.2</span>  -<span style='color: #BB0000;'>3.01</span>   -<span style='color: #BB0000;'>1.08</span> </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 7</span> -<span style='color: #BB0000;'>435.</span>  20.9  13.9   0.801  -<span style='color: #BB0000;'>0.916</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 8</span> -<span style='color: #BB0000;'>160.</span> -<span style='color: #BB0000;'>20.0</span> -<span style='color: #BB0000;'>23.3</span>  -<span style='color: #BB0000;'>1.06</span>    0.787</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 9</span> -<span style='color: #BB0000;'>172.</span>  10.8 -<span style='color: #BB0000;'>18.3</span>  -<span style='color: #BB0000;'>4.40</span>   -<span style='color: #BB0000;'>0.836</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>10</span> -<span style='color: #BB0000;'>209.</span>  19.7  -<span style='color: #BB0000;'>8.94</span> -<span style='color: #BB0000;'>2.58</span>    1.33 </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># ℹ 22 more rows</span></span></span>
<span></span></code></pre>
</div>
<p>Mmm. What happened to <code>mpg</code>? We mistakenly told <code>step_pca()</code> to perform PCA on <em>all</em> of the numeric variables, not just the numeric <em>predictors</em>! As a result, it incorporated <code>mpg</code> into the principal components, removing each of the original numeric variables after the fact. Rewriting using the correct tidyselect specification <code>all_numeric_predictors()</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>mtcars_rec_new</span> <span class='o'>&lt;-</span> </span>
<span>  <span class='nf'>recipe</span><span class='o'>(</span><span class='nv'>mpg</span> <span class='o'>~</span> <span class='nv'>.</span>, <span class='nv'>mtcars</span><span class='o'>)</span> <span class='o'>%&gt;%</span></span>
<span>  <span class='nf'>step_pca</span><span class='o'>(</span><span class='nf'>all_numeric_predictors</span><span class='o'>(</span><span class='o'>)</span><span class='o'>)</span></span>
<span></span>
<span><span class='nf'>workflow</span><span class='o'>(</span><span class='nv'>mtcars_rec_new</span>, <span class='nv'>linear_spec</span><span class='o'>)</span> <span class='o'>%&gt;%</span> <span class='nf'>fit</span><span class='o'>(</span><span class='nv'>mtcars</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; ══ Workflow [trained] ════════════════════════════════════════════════</span></span>
<span><span class='c'>#&gt; <span style='font-style: italic;'>Preprocessor:</span> Recipe</span></span>
<span><span class='c'>#&gt; <span style='font-style: italic;'>Model:</span> linear_reg()</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; ── Preprocessor ──────────────────────────────────────────────────────</span></span>
<span><span class='c'>#&gt; 1 Recipe Step</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; • step_pca()</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; ── Model ─────────────────────────────────────────────────────────────</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; Call:</span></span>
<span><span class='c'>#&gt; stats::lm(formula = ..y ~ ., data = data)</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; Coefficients:</span></span>
<span><span class='c'>#&gt; (Intercept)          PC1          PC2          PC3          PC4  </span></span>
<span><span class='c'>#&gt;    43.39293      0.07609     -0.05266      0.57892      0.94890  </span></span>
<span><span class='c'>#&gt;         PC5  </span></span>
<span><span class='c'>#&gt;    -1.72569</span></span>
<span></span></code></pre>
</div>
<p>Works like a charm. That error we saw previously could be much more helpful, though. With the current developmental version of parsnip, this looks like:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>fit</span><span class='o'>(</span><span class='nv'>linear_spec</span>, <span class='o'>~</span> <span class='nv'>hp</span>, <span class='nv'>mtcars</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> `linear_reg()` was unable to find an outcome.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>ℹ</span> Ensure that you have specified an outcome column and that it hasn't</span></span>
<span><span class='c'>#&gt;   been removed in pre-processing.</span></span>
<span></span></code></pre>
</div>
<p>Or, with workflows:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>workflow</span><span class='o'>(</span><span class='nv'>mtcars_rec</span>, <span class='nv'>linear_spec</span><span class='o'>)</span> <span class='o'>%&gt;%</span> <span class='nf'>fit</span><span class='o'>(</span><span class='nv'>mtcars</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> `linear_reg()` was unable to find an outcome.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>ℹ</span> Ensure that you have specified an outcome column and that it hasn't</span></span>
<span><span class='c'>#&gt;   been removed in pre-processing.</span></span>
<span></span></code></pre>
</div>
<p>Much better.</p>
<h2 id="predicting-with-things-that-cant-predict">Predicting with things that can&rsquo;t predict
</h2>
<p>Earlier this year, Dr. Louise E. Sinks put out a <a href="https://lsinks.github.io/posts/2023-04-10-tidymodels/tidymodels_tutorial.html" target="_blank" rel="noopener">wonderful blog post</a>
 documenting what it felt like to approach the various object types defined in the tidymodels as a newcomer to the collection of packages. They wrote:</p>
<blockquote>
<p>I found it confusing that <code>fit</code>, <code>last_fit</code>, <code>fit_resamples</code>, etc., did not all produce objects that contained the same information and could be acted on by the same functions.</p>
</blockquote>
<p>This makes sense. While we try to forefront the intended mental model for fitting and predicting with tidymodels in our APIs and documentation, we also need to be proactive in anticipating common challenges in constructing that mental model.</p>
<p>For example, we&rsquo;ve found that it&rsquo;s sometimes not clear to users which outputs they can call <a href="https://rdrr.io/r/stats/predict.html" target="_blank" rel="noopener"><code>predict()</code></a>
 on. One such situation, as Louise points out, is with <code>fit_resamples()</code>:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># fit a linear regression model to bootstrap resamples of mtcars</span></span>
<span><span class='nv'>mtcars_res</span> <span class='o'>&lt;-</span> <span class='nf'>fit_resamples</span><span class='o'>(</span><span class='nf'>linear_reg</span><span class='o'>(</span><span class='o'>)</span>, <span class='nv'>mpg</span> <span class='o'>~</span> <span class='nv'>.</span>, <span class='nf'>bootstraps</span><span class='o'>(</span><span class='nv'>mtcars</span><span class='o'>)</span><span class='o'>)</span></span>
<span></span>
<span><span class='nv'>mtcars_res</span></span>
<span><span class='c'>#&gt; # Resampling results</span></span>
<span><span class='c'>#&gt; # Bootstrap sampling </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># A tibble: 25 × 4</span></span></span>
<span><span class='c'>#&gt;    splits          id          .metrics         .notes          </span></span>
<span><span class='c'>#&gt;    <span style='color: #555555; font-style: italic;'>&lt;list&gt;</span>          <span style='color: #555555; font-style: italic;'>&lt;chr&gt;</span>       <span style='color: #555555; font-style: italic;'>&lt;list&gt;</span>           <span style='color: #555555; font-style: italic;'>&lt;list&gt;</span>          </span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 1</span> <span style='color: #555555;'>&lt;split [32/11]&gt;</span> Bootstrap01 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 2</span> <span style='color: #555555;'>&lt;split [32/10]&gt;</span> Bootstrap02 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 3</span> <span style='color: #555555;'>&lt;split [32/16]&gt;</span> Bootstrap03 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 4</span> <span style='color: #555555;'>&lt;split [32/11]&gt;</span> Bootstrap04 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 5</span> <span style='color: #555555;'>&lt;split [32/10]&gt;</span> Bootstrap05 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 6</span> <span style='color: #555555;'>&lt;split [32/13]&gt;</span> Bootstrap06 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 7</span> <span style='color: #555555;'>&lt;split [32/16]&gt;</span> Bootstrap07 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 8</span> <span style='color: #555555;'>&lt;split [32/11]&gt;</span> Bootstrap08 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'> 9</span> <span style='color: #555555;'>&lt;split [32/11]&gt;</span> Bootstrap09 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>10</span> <span style='color: #555555;'>&lt;split [32/10]&gt;</span> Bootstrap10 <span style='color: #555555;'>&lt;tibble [2 × 4]&gt;</span> <span style='color: #555555;'>&lt;tibble [0 × 3]&gt;</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'># ℹ 15 more rows</span></span></span>
<span></span></code></pre>
</div>
<p>With previous tidymodels versions, mistakenly trying to predict with this object resulted in the following output:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">predict</span><span class="p">(</span><span class="n">mtcars_res</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error in UseMethod(&#34;predict&#34;) : </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   no applicable method for &#39;predict&#39; applied to an object of class</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   &#34;c(&#39;resample_results&#39;, &#39;tune_results&#39;, &#39;tbl_df&#39;, &#39;tbl&#39;, &#39;data.frame&#39;)&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Some R developers may recognize this error as what results when we didn&rsquo;t define any <a href="https://rdrr.io/r/stats/predict.html" target="_blank" rel="noopener"><code>predict()</code></a>
 method for <code>tune_results</code> objects. We didn&rsquo;t do so because prediction isn&rsquo;t well-defined for tuning results. <em>But</em>, this error message does little to help a user understand why that&rsquo;s the case.</p>
<p>We&rsquo;ve recently made some changes to error more informatively in this case. We do so by defining a &ldquo;dummy&rdquo; <a href="https://rdrr.io/r/stats/predict.html" target="_blank" rel="noopener"><code>predict()</code></a>
 method for tuning results, implemented only for the sake of erroring more informatively. The same code will now give the following output:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">predict</span><span class="p">(</span><span class="n">mtcars_res</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error in `predict()`:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ! `predict()` is not well-defined for tuning results.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ℹ To predict with the optimal model configuration from tuning</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   results, ensure that the tuning result was generated with the</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   control option `save_workflow = TRUE`, run `fit_best()`, and</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   then predict using `predict()` on its output.</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ℹ To collect predictions from tuning results, ensure that the</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   tuning result was generated with the control option `save_pred</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   = TRUE` and run `collect_predictions()`.</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>References to important concepts or functions, like <a href="https://tune.tidymodels.org/reference/control_grid.html" target="_blank" rel="noopener">control options</a>
, <a href="https://tune.tidymodels.org/reference/fit_best.html?q=fit_best" target="_blank" rel="noopener"><code>fit_best()</code></a>
, and <a href="https://tune.tidymodels.org/reference/collect_predictions.html?q=collect" target="_blank" rel="noopener"><code>collect_predictions()</code></a>
, link to the help-files for those functions using <a href="https://cli.r-lib.org/reference/cli_abort.html" target="_blank" rel="noopener">cli&rsquo;s erroring tools</a>
.</p>
<p>We hope new error messages like this will help to get folks back on track.</p>
<h2 id="model-formulas">Model formulas
</h2>
<p>In R, formulas provide a compact, symbolic notation to specify model terms. Many modeling functions in R make use of &ldquo;specials,&rdquo; or nonstandard notations used in formulas. Specials are defined and handled as a special case by a given modeling package. parsnip defers to engine packages to handle specials, so you can work with them as usual. For example, the mgcv package provides support for generalized additive models in R, and defines a special called <code>s()</code> to indicate smoothing terms. You can interface with it via tidymodels like so:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'># define a generalized additive model specification</span></span>
<span><span class='nv'>gam_spec</span> <span class='o'>&lt;-</span> <span class='nf'>gen_additive_mod</span><span class='o'>(</span><span class='s'>"regression"</span><span class='o'>)</span></span>
<span></span>
<span><span class='c'># fit the specification using a formula with specials</span></span>
<span><span class='nf'>fit</span><span class='o'>(</span><span class='nv'>gam_spec</span>, <span class='nv'>mpg</span> <span class='o'>~</span> <span class='nv'>cyl</span> <span class='o'>+</span> <span class='nf'>s</span><span class='o'>(</span><span class='nv'>disp</span>, k <span class='o'>=</span> <span class='m'>5</span><span class='o'>)</span>, <span class='nv'>mtcars</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; parsnip model object</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; Family: gaussian </span></span>
<span><span class='c'>#&gt; Link function: identity </span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; Formula:</span></span>
<span><span class='c'>#&gt; mpg ~ cyl + s(disp, k = 5)</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; Estimated degrees of freedom:</span></span>
<span><span class='c'>#&gt; 3.39  total = 5.39 </span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; GCV score: 6.380152</span></span>
<span></span></code></pre>
</div>
<p>While parsnip can handle specials just fine, the package is often used in conjunction with the greater tidymodels package ecosystem, which defines its own pre-processing infrastructure and functionality via packages like hardhat and recipes. The specials defined in many modeling packages introduce conflicts with that infrastructure. To support specials while also maintaining consistent syntax elsewhere in the ecosystem, <strong>tidymodels delineates between two types of formulas: preprocessing formulas and model formulas</strong>. Preprocessing formulas determine the input variables, while model formulas determine the model structure.</p>
<p>This is a tricky abstraction, and one that users have tripped up on in the past. Users could generate all sorts of different errors by 1) mistakenly passing model formulas where preprocessing formulas were expected, or 2) forgetting to pass a model formula where it&rsquo;s needed. For an example of 1), we could pass recipes the same formula we passed to parsnip:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">recipe</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">cyl</span> <span class="o">+</span> <span class="nf">s</span><span class="p">(</span><span class="n">disp</span><span class="p">,</span> <span class="n">k</span> <span class="o">=</span> <span class="m">5</span><span class="p">),</span> <span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error in `inline_check()`:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ! No in-line functions should be used here; use steps to </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   define baking actions.</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>But we <em>just</em> used a special with another tidymodels function! Rude!</p>
<p>Or, to demonstrate 2), we pass the preprocessing formula as we ought to but forget to provide the model formula:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">gam_wflow</span> <span class="o">&lt;-</span> 
</span></span><span class="line"><span class="cl">  <span class="nf">workflow</span><span class="p">()</span> <span class="o">%&gt;%</span>
</span></span><span class="line"><span class="cl">  <span class="nf">add_formula</span><span class="p">(</span><span class="n">mpg</span> <span class="o">~</span> <span class="n">.)</span> <span class="o">%&gt;%</span>
</span></span><span class="line"><span class="cl">  <span class="nf">add_model</span><span class="p">(</span><span class="n">gam_spec</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gam_wflow</span> <span class="o">%&gt;%</span> <span class="nf">fit</span><span class="p">(</span><span class="n">mtcars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Error in `fit_xy()`:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ! `fit()` must be used with GAM models (due to its use of formulas).</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Uh, but I <em>did</em> just use <code>fit()</code>!</p>
<p>Since the distinction between model formulas and preprocessor formulas comes up in functions across tidymodels, we decide to create a <a href="https://parsnip.tidymodels.org/dev/reference/model_formula.html" target="_blank" rel="noopener">central page</a>
 that documents the concept itself, hopefully making the syntax associated with it come more easily to users. Then, we link to it <em>all over the place</em>. For example, those errors now look like:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>recipe</span><span class='o'>(</span><span class='nv'>mpg</span> <span class='o'>~</span> <span class='nv'>cyl</span> <span class='o'>+</span> <span class='nf'>s</span><span class='o'>(</span><span class='nv'>disp</span>, k <span class='o'>=</span> <span class='m'>5</span><span class='o'>)</span>, <span class='nv'>mtcars</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'> in `inline_check()`:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BB0000;'>✖</span> No in-line functions should be used here.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>ℹ</span> The following function was found: `s`.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>ℹ</span> Use steps to do transformations instead.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>ℹ</span> If your modeling engine uses special terms in formulas, pass that</span></span>
<span><span class='c'>#&gt;   formula to workflows as a model formula</span></span>
<span><span class='c'>#&gt;   (`?parsnip::model_formula()`).</span></span>
<span></span></code></pre>
</div>
<p>Or:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>gam_wflow</span> <span class='o'>%&gt;%</span> <span class='nf'>fit</span><span class='o'>(</span><span class='nv'>mtcars</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00; font-weight: bold;'>Error</span><span style='font-weight: bold;'>:</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BBBB00;'>!</span> When working with generalized additive models, please supply</span></span>
<span><span class='c'>#&gt;   the model specification to `workflows::add_model()` along with a</span></span>
<span><span class='c'>#&gt;   `formula` argument.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>ℹ</span> See `?parsnip::model_formula()` to learn more.</span></span>
<span></span></code></pre>
</div>
<p>While I&rsquo;ve only outlined three, there are all sorts of improvements to error messages on their way to the tidymodels packages in upcoming releases. If you happen to stumble across them, we hope they quickly set you back on the right path. 🗺</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Issue triage consists of categorizing, prioritizing, and consolidating issues in a repository&rsquo;s issue tracker.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>See the <a href="https://tidyclust.tidymodels.org" target="_blank" rel="noopener">tidyclust</a>
 package for unsupervised learning with tidymodels!&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2023/tidymodels-errors-q4/thumbnail-wd.jpg" length="650472" type="image/jpeg" />
    </item>
    <item>
      <title>testthat 3.2.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2023/testthat-3-2-0/</link>
      <pubDate>Sun, 08 Oct 2023 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2023/testthat-3-2-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re chuffed to announce the release of <a href="http://testthat.r-lib.org/" target="_blank" rel="noopener">testthat</a>
 3.2.0. testthat makes it easy to turn your existing informal tests into formal, automated tests that you can rerun quickly and easily. testthat is the most popular unit-testing package for R, and is used by almost 9,000 CRAN and Bioconductor packages. You can learn more about unit testing at <a href="https://r-pkgs.org/tests.html" target="_blank" rel="noopener">https://r-pkgs.org/tests.html</a>
.</p>
<p>You can install it from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"testthat"</span><span class='o'>)</span></span></code></pre>
</div>
<p>testthat 3.2.0 includes relatively few new features but there have been nine patch releases since testthat 3.1.0. These patch releases contained a bunch of experiments that we now believe are ready for the world. So this blog post summarises the changes in <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.1" target="_blank" rel="noopener">3.1.1</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.2" target="_blank" rel="noopener">3.1.2</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.3" target="_blank" rel="noopener">3.1.3</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.4" target="_blank" rel="noopener">3.1.4</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.5" target="_blank" rel="noopener">3.1.5</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.6" target="_blank" rel="noopener">3.1.6</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.7" target="_blank" rel="noopener">3.1.7</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.8" target="_blank" rel="noopener">3.1.8</a>
, <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.9" target="_blank" rel="noopener">3.1.9</a>
, and <a href="https://github.com/r-lib/testthat/releases/tag/v3.1.10" target="_blank" rel="noopener">3.1.10</a>
 over the last two years.</p>
<p>Here we&rsquo;ll focus on the biggest news: new expectations, tweaks to the way that error snapshots are reported, support for mocking, a new way to detect if a test has changed global state, and a bunch of smaller UI improvements.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://testthat.r-lib.org'>testthat</a></span><span class='o'>)</span></span></code></pre>
</div>
<h2 id="documentation">Documentation
</h2>
<p>The first and most important thing to point out is that the second edition of <a href="https://r-pkgs.org" target="_blank" rel="noopener">R Packages</a>
 contains updated and much expanded coverage of testing. Coverage of testing is now split up over three chapters:</p>
<ul>
<li><a href="https://r-pkgs.org/testing-basics.html" target="_blank" rel="noopener">Testing basics</a>
</li>
<li><a href="https://r-pkgs.org/testing-design.html" target="_blank" rel="noopener">Designing your test suite</a>
</li>
<li><a href="https://r-pkgs.org/testing-advanced.html" target="_blank" rel="noopener">Advanced testing techniques</a>
</li>
</ul>
<p>There&rsquo;s also a new vignette about special files (<a href="https://testthat.r-lib.org/articles/special-files.html" target="_blank" rel="noopener"><code>vignette(&quot;special-files&quot;)</code></a>
) which describes the various special files that you find in <code>tests/testthat</code> and when you might need to use them.</p>
<h2 id="new-expectations">New expectations
</h2>
<p>There are a handful of notable new expectations. <a href="https://testthat.r-lib.org/reference/expect_setequal.html" target="_blank" rel="noopener"><code>expect_contains()</code></a>
 and <a href="https://testthat.r-lib.org/reference/expect_setequal.html" target="_blank" rel="noopener"><code>expect_in()</code></a>
 work similarly to <code>expect_true(all(expected %in% object))</code> or <code>expect_true(all(object %in% expected))</code> but give more informative failure messages:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>fruits</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"apple"</span>, <span class='s'>"banana"</span>, <span class='s'>"pear"</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://testthat.r-lib.org/reference/expect_setequal.html'>expect_contains</a></span><span class='o'>(</span><span class='nv'>fruits</span>, <span class='s'>"apple"</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://testthat.r-lib.org/reference/expect_setequal.html'>expect_contains</a></span><span class='o'>(</span><span class='nv'>fruits</span>, <span class='s'>"pineapple"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Error: `fruits` (`actual`) doesn't fully contain all the values in "pineapple" (`expected`).</span></span>
<span><span class='c'>#&gt; * Missing from `actual`: "pineapple"</span></span>
<span><span class='c'>#&gt; * Present in `actual`:   "apple", "banana", "pear"</span></span>
<span></span><span></span>
<span><span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='kc'>TRUE</span>, <span class='kc'>FALSE</span>, <span class='kc'>TRUE</span>, <span class='kc'>FALSE</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://testthat.r-lib.org/reference/expect_setequal.html'>expect_in</a></span><span class='o'>(</span><span class='nv'>x</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='kc'>TRUE</span>, <span class='kc'>FALSE</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='nv'>x</span> <span class='o'>&lt;-</span> <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='kc'>TRUE</span>, <span class='kc'>FALSE</span>, <span class='kc'>TRUE</span>, <span class='kc'>NA</span>, <span class='kc'>FALSE</span><span class='o'>)</span></span>
<span><span class='nf'><a href='https://testthat.r-lib.org/reference/expect_setequal.html'>expect_in</a></span><span class='o'>(</span><span class='nv'>x</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='kc'>TRUE</span>, <span class='kc'>FALSE</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Error: `x` (`actual`) isn't fully contained within c(TRUE, FALSE) (`expected`).</span></span>
<span><span class='c'>#&gt; * Missing from `expected`: NA</span></span>
<span><span class='c'>#&gt; * Present in `expected`:   TRUE, FALSE</span></span>
<span></span></code></pre>
</div>
<p><a href="https://testthat.r-lib.org/reference/expect_no_error.html" target="_blank" rel="noopener"><code>expect_no_error()</code></a>
, <a href="https://testthat.r-lib.org/reference/expect_no_error.html" target="_blank" rel="noopener"><code>expect_no_warning()</code></a>
, and <a href="https://testthat.r-lib.org/reference/expect_no_error.html" target="_blank" rel="noopener"><code>expect_no_message()</code></a>
 make it easier (and clearer) to confirm that code runs without errors, warnings, or messages. The default fails if there is any error/warning/message, but you can optionally supply either the <code>message</code> or <code>class</code> arguments to confirm the absence of a specific error/warning/message.</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>foo</span> <span class='o'>&lt;-</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>x</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='kr'>if</span> <span class='o'>(</span><span class='nv'>x</span> <span class='o'>&lt;</span> <span class='m'>0</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>    <span class='nv'>x</span> <span class='o'>+</span> <span class='s'>"10"</span></span>
<span>  <span class='o'>&#125;</span> <span class='kr'>else</span> <span class='o'>&#123;</span></span>
<span>    <span class='nv'>x</span> <span class='o'>=</span> <span class='m'>20</span></span>
<span>  <span class='o'>&#125;</span></span>
<span><span class='o'>&#125;</span></span>
<span></span>
<span><span class='nf'><a href='https://testthat.r-lib.org/reference/expect_no_error.html'>expect_no_error</a></span><span class='o'>(</span><span class='nf'>foo</span><span class='o'>(</span><span class='o'>-</span><span class='m'>10</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Error: Expected `foo(-10)` to run without any errors.</span></span>
<span><span class='c'>#&gt; <span style='color: #0000BB;'>ℹ</span> Actually got a &lt;simpleError&gt; with text:</span></span>
<span><span class='c'>#&gt;   non-numeric argument to binary operator</span></span>
<span></span><span></span>
<span><span class='c'># No difference here but will lead to a better failure later</span></span>
<span><span class='c'># once you've fixed this problem and later introduce a new one</span></span>
<span><span class='nf'><a href='https://testthat.r-lib.org/reference/expect_no_error.html'>expect_no_error</a></span><span class='o'>(</span><span class='nf'>foo</span><span class='o'>(</span><span class='o'>-</span><span class='m'>10</span><span class='o'>)</span>, message <span class='o'>=</span> <span class='s'>"non-numeric argument"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Error: Expected `foo(-10)` to run without any errors matching pattern 'non-numeric argument'.</span></span>
<span><span class='c'>#&gt; <span style='color: #0000BB;'>ℹ</span> Actually got a &lt;simpleError&gt; with text:</span></span>
<span><span class='c'>#&gt;   non-numeric argument to binary operator</span></span>
<span></span></code></pre>
</div>
<h2 id="snapshotting-changes">Snapshotting changes
</h2>
<p><code>expect_snapshot(error = TRUE)</code> has a new display of error messages that strives to be closer to what you see interactively. In particular, you&rsquo;ll no longer see the error class and you will now see the error call.</p>
<ul>
<li>
<p>Old display:</p>
<pre><code>Code
  f()
Error &lt;simpleError&gt;
  baz
</code></pre>
</li>
<li>
<p>New display:</p>
<pre><code>Code
  f()
Condition
  Error in `f()`:
  ! baz
</code></pre>
</li>
</ul>
<p>If you have used <code>expect_snapshot(error = TRUE)</code> in your package, this means that you will need to re-run and approve your snapshots. We hope this is not too annoying and we believe it is worth it given the more accurate reflection of generated error messages. This will not affect checks on CRAN because, by default, snapshot tests are not run on CRAN.</p>
<h2 id="mocking">Mocking
</h2>
<p>Mocking<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> is a tool for temporarily replacing the implementation of a function in order to make testing easier. Sometimes when testing a function, one part of it is challenging to run in your test environment (maybe it requires human interaction, a live database connection, or maybe it just takes a long time to run). For example, take the following imaginary function. It has a bunch of straightforward computation that would be easy to test but right in the middle of the function it calls <code>complicated()</code> which is hard to test:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nv'>my_function</span> <span class='o'>&lt;-</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>x</span>, <span class='nv'>y</span>, <span class='nv'>z</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nv'>a</span> <span class='o'>&lt;-</span> <span class='nf'>f</span><span class='o'>(</span><span class='nv'>x</span>, <span class='nv'>y</span><span class='o'>)</span></span>
<span>  <span class='nv'>b</span> <span class='o'>&lt;-</span> <span class='nf'>g</span><span class='o'>(</span><span class='nv'>y</span>, <span class='nv'>z</span><span class='o'>)</span></span>
<span>  <span class='nv'>c</span> <span class='o'>&lt;-</span> <span class='nf'>h</span><span class='o'>(</span><span class='nv'>a</span>, <span class='nv'>b</span><span class='o'>)</span></span>
<span>  </span>
<span>  <span class='nv'>d</span> <span class='o'>&lt;-</span> <span class='nf'>complicated</span><span class='o'>(</span><span class='nv'>c</span><span class='o'>)</span></span>
<span>  </span>
<span>  <span class='nf'>i</span><span class='o'>(</span><span class='nv'>d</span>, <span class='m'>1</span>, <span class='kc'>TRUE</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span></span></code></pre>
</div>
<p>Mocking allows you to temporarily replace <code>complicated()</code> with something simpler, allowing you to test the rest of the function. testthat now supports mocking with <a href="https://testthat.r-lib.org/reference/local_mocked_bindings.html" target="_blank" rel="noopener"><code>local_mocked_bindings()</code></a>
, which temporarily replaces the implementation of a function. For example, to test <code>my_function()</code> you might write something like this:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"my_function() returns expected result"</span>, <span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/local_mocked_bindings.html'>local_mocked_bindings</a></span><span class='o'>(</span></span>
<span>    complicated <span class='o'>=</span> <span class='kr'>function</span><span class='o'>(</span><span class='nv'>x</span><span class='o'>)</span> <span class='kc'>TRUE</span></span>
<span>  <span class='o'>)</span></span>
<span>  <span class='nv'>...</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>testthat has a complicated past with mocking. testthat introduced <a href="https://testthat.r-lib.org/reference/with_mock.html" target="_blank" rel="noopener"><code>with_mock()</code></a>
 in v0.9 (way back in 2014), but we started discovering problems with the implementation in v2.0.0 (2017) leading to its deprecation in v3.0.0 (2020). A few packages arose to fill the gap (like <a href="https://github.com/r-lib/mockery" target="_blank" rel="noopener">mockery</a>
, <a href="https://krlmlr.github.io/mockr/" target="_blank" rel="noopener">mockr</a>
, and <a href="https://nbenn.github.io/mockthat/" target="_blank" rel="noopener">mockthat</a>
) but none of their implementations were completely satisfactory. Earlier this year a new approach occurred to me that avoids many of the problems of the previous approaches. This is now implemented in <a href="https://testthat.r-lib.org/reference/local_mocked_bindings.html" target="_blank" rel="noopener"><code>with_mocked_bindings()</code></a>
 and <a href="https://testthat.r-lib.org/reference/local_mocked_bindings.html" target="_blank" rel="noopener"><code>local_mocked_bindings()</code></a>
; we&rsquo;ve been using these new functions for a few months now without problems, and it feels like time to announce to the world.</p>
<h2 id="state-inspector">State inspector
</h2>
<p>In times gone by it was very easy to accidentally change the state of the world in a test:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/test_that.html'>test_that</a></span><span class='o'>(</span><span class='s'>"side-by-side diffs work"</span>, <span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/options.html'>options</a></span><span class='o'>(</span>width <span class='o'>=</span> <span class='m'>20</span><span class='o'>)</span></span>
<span>  <span class='nf'><a href='https://testthat.r-lib.org/reference/expect_snapshot.html'>expect_snapshot</a></span><span class='o'>(</span></span>
<span>    <span class='nf'>waldo</span><span class='nf'>::</span><span class='nf'><a href='https://waldo.r-lib.org/reference/compare.html'>compare</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"X"</span>, <span class='nv'>letters</span><span class='o'>)</span>, <span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='nv'>letters</span>, <span class='s'>"X"</span><span class='o'>)</span><span class='o'>)</span></span>
<span>  <span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>When you look at a single test it&rsquo;s easy to spot the problem, and switch to a more appropriate way of temporarily changing the options, like <a href="https://withr.r-lib.org/reference/with_options.html" target="_blank" rel="noopener"><code>withr::local_options()</code></a>
. But sometimes this mistake crept in a long time ago and is now hiding amongst hundreds or thousands of tests.</p>
<p>In earlier versions of testthat, finding tests that accidentally changed the world was painful: the only way was to painstakingly review each test. Now you can use <a href="https://testthat.r-lib.org/reference/set_state_inspector.html" target="_blank" rel="noopener"><code>set_state_inspector()</code></a>
 to register a function that&rsquo;s called before and after every test. If the function returns different values, testthat will let you know. You&rsquo;ll typically do this either in <code>tests/testhat/setup.R</code> or an existing helper file.</p>
<p>So, for example, to detect if any of your tests have modified options you could use this state inspector:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/set_state_inspector.html'>set_state_inspector</a></span><span class='o'>(</span><span class='kr'>function</span><span class='o'>(</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span>options <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/options.html'>options</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>Or maybe you&rsquo;ve seen an <code>R CMD check</code> warning that you&rsquo;ve forgotten to close a connection:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://testthat.r-lib.org/reference/set_state_inspector.html'>set_state_inspector</a></span><span class='o'>(</span><span class='kr'>function</span><span class='o'>(</span><span class='o'>)</span> <span class='o'>&#123;</span></span>
<span>  <span class='nf'><a href='https://rdrr.io/r/base/list.html'>list</a></span><span class='o'>(</span>connections <span class='o'>=</span> <span class='nf'><a href='https://rdrr.io/r/base/nrow.html'>nrow</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/showConnections.html'>showConnections</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='o'>&#125;</span><span class='o'>)</span></span></code></pre>
</div>
<p>And you can of course combine multiple checks just by returning a more complicated list.</p>
<h2 id="ui-improvements">UI improvements
</h2>
<p>testthat 3.2.0 includes a bunch of minor user interface improvements that should make day-to-day use of testthat more enjoyable. Some of our favourite highlights are:</p>
<ul>
<li>Parallel testing now works much better with snapshot tests. (And updates to the processx package means that testthat no longer leaves processes around if you terminate a test process early.)</li>
<li>We use an improved algorithm to find the source reference associated with an expectation/error/warning/skip. We now look for the most recent call (within inside <a href="https://testthat.r-lib.org/reference/test_that.html" target="_blank" rel="noopener"><code>test_that()</code></a>
 that has known source. This generally gives more specific locations than the previous approach and gives much better locations if an error occurs in an exit handler.</li>
<li>Tracebacks are no longer truncated and we use rlang&rsquo;s default tree display; this should make it easier to track down problems when testing in non-interactive contexts.</li>
<li>Assuming you have a recent RStudio, test failures are now clickable, taking you to the line where the problem occurred. Similarly, when a snapshot test changes, you can now click that suggested code to run the appropriate <a href="https://testthat.r-lib.org/reference/snapshot_accept.html" target="_blank" rel="noopener"><code>snapshot_accept()</code></a>
 call.</li>
<li>Skips are now only shown at the end of reporter summaries, not as tests are run. This makes them less intrusive in interactive tests while still allowing you to verify that the correct tests are skipped.</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thanks to all 127 contributors who helped make these last 10 release of testthat happen, whether it be through contributed code or filing issues: <a href="https://github.com/ALanguillaume" target="_blank" rel="noopener">@ALanguillaume</a>
, <a href="https://github.com/alessandroaccettulli" target="_blank" rel="noopener">@alessandroaccettulli</a>
, <a href="https://github.com/ambica-aas" target="_blank" rel="noopener">@ambica-aas</a>
, <a href="https://github.com/annweideman" target="_blank" rel="noopener">@annweideman</a>
, <a href="https://github.com/aronatkins" target="_blank" rel="noopener">@aronatkins</a>
, <a href="https://github.com/ashander" target="_blank" rel="noopener">@ashander</a>
, <a href="https://github.com/AshesITR" target="_blank" rel="noopener">@AshesITR</a>
, <a href="https://github.com/astayleraz" target="_blank" rel="noopener">@astayleraz</a>
, <a href="https://github.com/ateucher" target="_blank" rel="noopener">@ateucher</a>
, <a href="https://github.com/avraam-inside" target="_blank" rel="noopener">@avraam-inside</a>
, <a href="https://github.com/b-steve" target="_blank" rel="noopener">@b-steve</a>
, <a href="https://github.com/bersbersbers" target="_blank" rel="noopener">@bersbersbers</a>
, <a href="https://github.com/billdenney" target="_blank" rel="noopener">@billdenney</a>
, <a href="https://github.com/Bisaloo" target="_blank" rel="noopener">@Bisaloo</a>
, <a href="https://github.com/cboettig" target="_blank" rel="noopener">@cboettig</a>
, <a href="https://github.com/cderv" target="_blank" rel="noopener">@cderv</a>
, <a href="https://github.com/chendaniely" target="_blank" rel="noopener">@chendaniely</a>
, <a href="https://github.com/ChrisBeeley" target="_blank" rel="noopener">@ChrisBeeley</a>
, <a href="https://github.com/ColinFay" target="_blank" rel="noopener">@ColinFay</a>
, <a href="https://github.com/CorradoLanera" target="_blank" rel="noopener">@CorradoLanera</a>
, <a href="https://github.com/daattali" target="_blank" rel="noopener">@daattali</a>
, <a href="https://github.com/damianooldoni" target="_blank" rel="noopener">@damianooldoni</a>
, <a href="https://github.com/DanChaltiel" target="_blank" rel="noopener">@DanChaltiel</a>
, <a href="https://github.com/danielinteractive" target="_blank" rel="noopener">@danielinteractive</a>
, <a href="https://github.com/DavisVaughan" target="_blank" rel="noopener">@DavisVaughan</a>
, <a href="https://github.com/daynefiler" target="_blank" rel="noopener">@daynefiler</a>
, <a href="https://github.com/dbdimitrov" target="_blank" rel="noopener">@dbdimitrov</a>
, <a href="https://github.com/dcaseykc" target="_blank" rel="noopener">@dcaseykc</a>
, <a href="https://github.com/dgkf" target="_blank" rel="noopener">@dgkf</a>
, <a href="https://github.com/dhicks" target="_blank" rel="noopener">@dhicks</a>
, <a href="https://github.com/dimfalk" target="_blank" rel="noopener">@dimfalk</a>
, <a href="https://github.com/dougwyu" target="_blank" rel="noopener">@dougwyu</a>
, <a href="https://github.com/dpprdan" target="_blank" rel="noopener">@dpprdan</a>
, <a href="https://github.com/dvg-p4" target="_blank" rel="noopener">@dvg-p4</a>
, <a href="https://github.com/elong0527" target="_blank" rel="noopener">@elong0527</a>
, <a href="https://github.com/Enchufa2" target="_blank" rel="noopener">@Enchufa2</a>
, <a href="https://github.com/etiennebacher" target="_blank" rel="noopener">@etiennebacher</a>
, <a href="https://github.com/FlippieCoetser" target="_blank" rel="noopener">@FlippieCoetser</a>
, <a href="https://github.com/florisvdh" target="_blank" rel="noopener">@florisvdh</a>
, <a href="https://github.com/gaborcsardi" target="_blank" rel="noopener">@gaborcsardi</a>
, <a href="https://github.com/gareth-j" target="_blank" rel="noopener">@gareth-j</a>
, <a href="https://github.com/gavinsimpson" target="_blank" rel="noopener">@gavinsimpson</a>
, <a href="https://github.com/ghill-fusion" target="_blank" rel="noopener">@ghill-fusion</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/heavywatal" target="_blank" rel="noopener">@heavywatal</a>
, <a href="https://github.com/hfrick" target="_blank" rel="noopener">@hfrick</a>
, <a href="https://github.com/hhau" target="_blank" rel="noopener">@hhau</a>
, <a href="https://github.com/hpages" target="_blank" rel="noopener">@hpages</a>
, <a href="https://github.com/hsloot" target="_blank" rel="noopener">@hsloot</a>
, <a href="https://github.com/hughjonesd" target="_blank" rel="noopener">@hughjonesd</a>
, <a href="https://github.com/IndrajeetPatil" target="_blank" rel="noopener">@IndrajeetPatil</a>
, <a href="https://github.com/jameslairdsmith" target="_blank" rel="noopener">@jameslairdsmith</a>
, <a href="https://github.com/jamieRowen" target="_blank" rel="noopener">@jamieRowen</a>
, <a href="https://github.com/jayruffell" target="_blank" rel="noopener">@jayruffell</a>
, <a href="https://github.com/JBGruber" target="_blank" rel="noopener">@JBGruber</a>
, <a href="https://github.com/jennybc" target="_blank" rel="noopener">@jennybc</a>
, <a href="https://github.com/JohnCoene" target="_blank" rel="noopener">@JohnCoene</a>
, <a href="https://github.com/jonathanvoelkle" target="_blank" rel="noopener">@jonathanvoelkle</a>
, <a href="https://github.com/jonthegeek" target="_blank" rel="noopener">@jonthegeek</a>
, <a href="https://github.com/josherrickson" target="_blank" rel="noopener">@josherrickson</a>
, <a href="https://github.com/kalaschnik" target="_blank" rel="noopener">@kalaschnik</a>
, <a href="https://github.com/kapsner" target="_blank" rel="noopener">@kapsner</a>
, <a href="https://github.com/kevinushey" target="_blank" rel="noopener">@kevinushey</a>
, <a href="https://github.com/kjytay" target="_blank" rel="noopener">@kjytay</a>
, <a href="https://github.com/krivit" target="_blank" rel="noopener">@krivit</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/larmarange" target="_blank" rel="noopener">@larmarange</a>
, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>
, <a href="https://github.com/llrs" target="_blank" rel="noopener">@llrs</a>
, <a href="https://github.com/luma-sb" target="_blank" rel="noopener">@luma-sb</a>
, <a href="https://github.com/machow" target="_blank" rel="noopener">@machow</a>
, <a href="https://github.com/maciekbanas" target="_blank" rel="noopener">@maciekbanas</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/majr-red" target="_blank" rel="noopener">@majr-red</a>
, <a href="https://github.com/maksymiuks" target="_blank" rel="noopener">@maksymiuks</a>
, <a href="https://github.com/mardam" target="_blank" rel="noopener">@mardam</a>
, <a href="https://github.com/MarkMc1089" target="_blank" rel="noopener">@MarkMc1089</a>
, <a href="https://github.com/markschat" target="_blank" rel="noopener">@markschat</a>
, <a href="https://github.com/MatthieuStigler" target="_blank" rel="noopener">@MatthieuStigler</a>
, <a href="https://github.com/maurolepore" target="_blank" rel="noopener">@maurolepore</a>
, <a href="https://github.com/maxheld83" target="_blank" rel="noopener">@maxheld83</a>
, <a href="https://github.com/mbojan" target="_blank" rel="noopener">@mbojan</a>
, <a href="https://github.com/mcol" target="_blank" rel="noopener">@mcol</a>
, <a href="https://github.com/mgirlich" target="_blank" rel="noopener">@mgirlich</a>
, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>
, <a href="https://github.com/mkb13" target="_blank" rel="noopener">@mkb13</a>
, <a href="https://github.com/mkoohafkan" target="_blank" rel="noopener">@mkoohafkan</a>
, <a href="https://github.com/MKyhos" target="_blank" rel="noopener">@MKyhos</a>
, <a href="https://github.com/moodymudskipper" target="_blank" rel="noopener">@moodymudskipper</a>
, <a href="https://github.com/Mosk915" target="_blank" rel="noopener">@Mosk915</a>
, <a href="https://github.com/mpjashby" target="_blank" rel="noopener">@mpjashby</a>
, <a href="https://github.com/ms609" target="_blank" rel="noopener">@ms609</a>
, <a href="https://github.com/mtmorgan" target="_blank" rel="noopener">@mtmorgan</a>
, <a href="https://github.com/musvaage" target="_blank" rel="noopener">@musvaage</a>
, <a href="https://github.com/nealrichardson" target="_blank" rel="noopener">@nealrichardson</a>
, <a href="https://github.com/netique" target="_blank" rel="noopener">@netique</a>
, <a href="https://github.com/njtierney" target="_blank" rel="noopener">@njtierney</a>
, <a href="https://github.com/olivroy" target="_blank" rel="noopener">@olivroy</a>
, <a href="https://github.com/osorensen" target="_blank" rel="noopener">@osorensen</a>
, <a href="https://github.com/pbulsink" target="_blank" rel="noopener">@pbulsink</a>
, <a href="https://github.com/peterdesmet" target="_blank" rel="noopener">@peterdesmet</a>
, <a href="https://github.com/r2evans" target="_blank" rel="noopener">@r2evans</a>
, <a href="https://github.com/radbasa" target="_blank" rel="noopener">@radbasa</a>
, <a href="https://github.com/remlapmot" target="_blank" rel="noopener">@remlapmot</a>
, <a href="https://github.com/rfineman" target="_blank" rel="noopener">@rfineman</a>
, <a href="https://github.com/rgayler" target="_blank" rel="noopener">@rgayler</a>
, <a href="https://github.com/romainfrancois" target="_blank" rel="noopener">@romainfrancois</a>
, <a href="https://github.com/s-fleck" target="_blank" rel="noopener">@s-fleck</a>
, <a href="https://github.com/salim-b" target="_blank" rel="noopener">@salim-b</a>
, <a href="https://github.com/schloerke" target="_blank" rel="noopener">@schloerke</a>
, <a href="https://github.com/sorhawell" target="_blank" rel="noopener">@sorhawell</a>
, <a href="https://github.com/StatisMike" target="_blank" rel="noopener">@StatisMike</a>
, <a href="https://github.com/StatsMan53" target="_blank" rel="noopener">@StatsMan53</a>
, <a href="https://github.com/stela2502" target="_blank" rel="noopener">@stela2502</a>
, <a href="https://github.com/stla" target="_blank" rel="noopener">@stla</a>
, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>
, <a href="https://github.com/tansaku" target="_blank" rel="noopener">@tansaku</a>
, <a href="https://github.com/tomliptrot" target="_blank" rel="noopener">@tomliptrot</a>
, <a href="https://github.com/torres-pedro" target="_blank" rel="noopener">@torres-pedro</a>
, <a href="https://github.com/wes-brooks" target="_blank" rel="noopener">@wes-brooks</a>
, <a href="https://github.com/wfmueller29" target="_blank" rel="noopener">@wfmueller29</a>
, <a href="https://github.com/wleoncio" target="_blank" rel="noopener">@wleoncio</a>
, <a href="https://github.com/wurli" target="_blank" rel="noopener">@wurli</a>
, <a href="https://github.com/yogat3ch" target="_blank" rel="noopener">@yogat3ch</a>
, <a href="https://github.com/yuliaUU" target="_blank" rel="noopener">@yuliaUU</a>
, <a href="https://github.com/yutannihilation" target="_blank" rel="noopener">@yutannihilation</a>
, and <a href="https://github.com/zsigmas" target="_blank" rel="noopener">@zsigmas</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Think mimicking, like a mockingbird, not making fun of.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2023/testthat-3-2-0/thumbnail-wd.jpg" length="273742" type="image/jpeg" />
    </item>
    <item>
      <title>pak 0.6.0</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2023/pak-0-6-0/</link>
      <pubDate>Tue, 05 Sep 2023 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2023/pak-0-6-0/</guid>
      <dc:creator>Gábor Csárdi</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
-->
<p>We&rsquo;re delighted to announce the release of <a href="https://pak.r-lib.org" target="_blank" rel="noopener">pak</a>
 0.6.0. pak helps with the installation of R packages and many related tasks.</p>
<p>You can install pak from CRAN with:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/utils/install.packages.html'>install.packages</a></span><span class='o'>(</span><span class='s'>"pak"</span><span class='o'>)</span></span></code></pre>
</div>
<p>If you use an older R version, or a platform that CRAN does not have binary packages for, it is faster and simpler to install pak from our repository. <a href="https://pak.r-lib.org/reference/install.html" target="_blank" rel="noopener">See the details in the manual.</a>
</p>
<p>This blog post focuses on the exciting new improvements in the matching and installation of system requirements on Linux systems.</p>
<p>You can see a full list of changes in the <a href="https://github.com/r-lib/pak/releases/tag/v0.6.0" target="_blank" rel="noopener">release notes</a>
</p>
<h2 id="system-requirements">System requirements
</h2>
<p>Many R packages require the installation of external software, otherwise they do not work, or even load. For example, the RPostgres R package requires the PostgreSQL client library, and by default dynamically links to it on Linux systems. This means that you (or the administrators of your system) need to install this library, typically in the form of a system package: <code>libpq-dev</code> on Ubuntu and Debian systems, or <code>postgresql-server-devel</code> or <code>postgresql-devel</code> on Red Hat, Fedora, etc. systems.</p>
<p>The good news is that pak now helps you with this:</p>
<ul>
<li>it looks up the required system packages when installing R packages,</li>
<li>it lets you know if any required system packages are missing from your system, before the installation, and</li>
<li>it installs them automatically, if you are a superuser, or if you can use password-less <code>sudo</code> to start a superuser shell.</li>
</ul>
<p>In addition, pak now also has some functions to query system requirements and system packages.</p>
<h2 id="supported-platforms">Supported platforms
</h2>
<p>pak 0.6.0 supports the following Linux systems currently:</p>
<ul>
<li>Ubuntu Linux,</li>
<li>Debian Linux,</li>
<li>Red Hat Enterprise Linux,</li>
<li>SUSE Linux Enterprise,</li>
<li>OpenSUSE,</li>
<li>CentOS,</li>
<li>Rocky Linux,</li>
<li>Fedora Linux.</li>
</ul>
<p>Call <a href="https://pak.r-lib.org/reference/sysreqs_platforms.html" target="_blank" rel="noopener"><code>pak::sysreqs_platforms()</code></a>
 to query the current list of supported platforms:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/sysreqs_platforms.html'>sysreqs_platforms</a></span><span class='o'>(</span><span class='o'>)</span><span class='o'>[</span>,<span class='m'>1</span><span class='o'>:</span><span class='m'>3</span><span class='o'>]</span></span>
<span><span class='c'>#&gt;                        name    os distribution</span></span>
<span><span class='c'>#&gt; 1              Ubuntu Linux linux       ubuntu</span></span>
<span><span class='c'>#&gt; 2              Debian Linux linux       debian</span></span>
<span><span class='c'>#&gt; 3              CentOS Linux linux       centos</span></span>
<span><span class='c'>#&gt; 4               Rocky Linux linux   rockylinux</span></span>
<span><span class='c'>#&gt; 5  Red Hat Enterprise Linux linux       redhat</span></span>
<span><span class='c'>#&gt; 6  Red Hat Enterprise Linux linux       redhat</span></span>
<span><span class='c'>#&gt; 7  Red Hat Enterprise Linux linux       redhat</span></span>
<span><span class='c'>#&gt; 8              Fedora Linux linux       fedora</span></span>
<span><span class='c'>#&gt; 9            openSUSE Linux linux     opensuse</span></span>
<span><span class='c'>#&gt; 10    SUSE Linux Enterprise linux          sle</span></span>
<span></span></code></pre>
</div>
<p>Call <a href="https://pak.r-lib.org/reference/system_r_platform.html" target="_blank" rel="noopener"><code>pak::system_r_platform()</code></a>
 to check if pak has detected your platform correctly, and <a href="https://pak.r-lib.org/reference/sysreqs_is_supported.html" target="_blank" rel="noopener"><code>pak::sysreqs_is_supported()</code></a>
 to see if it is supported:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/system_r_platform.html'>system_r_platform</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] "x86_64-pc-linux-gnu-ubuntu-22.04"</span></span>
<span></span></code></pre>
</div>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/sysreqs_is_supported.html'>sysreqs_is_supported</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; [1] TRUE</span></span>
<span></span></code></pre>
</div>
<h2 id="r-package-installation">R package installation
</h2>
<p>If you are using pak as the <code>root</code> user, on a supported platform, then during package installation pak will look up the required system packages, and will install the missing ones. Here is an example:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/pkg_install.html'>pkg_install</a></span><span class='o'>(</span><span class='s'>"RPostgres"</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Loading metadata database<span style='color: #00BB00;'>v</span> Loading metadata database ... done</span></span>
<span><span class='c'>#&gt;  </span></span>
<span><span class='c'>#&gt; &gt; Will <span style='font-style: italic;'>install</span> 12 packages.</span></span>
<span><span class='c'>#&gt; &gt; Will <span style='font-style: italic;'>download</span> 12 packages with unknown size.</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>DBI</span>          1.1.3  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>RPostgres</span>    1.4.5  [dl]<span style='color: #555555;'> + </span><span style='color: #BB0000;'>x</span><span style='color: #00BBBB;'> libpq-dev</span></span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>Rcpp</span>         1.0.11 [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>bit</span>          4.0.5  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>bit64</span>        4.0.5  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>blob</span>         1.2.4  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>generics</span>     0.1.3  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>hms</span>          1.1.3  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>lubridate</span>    1.9.2  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>pkgconfig</span>    2.0.3  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>timechange</span>   0.2.0  [dl]</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>withr</span>        2.5.0  [dl]</span></span>
<span><span class='c'>#&gt; &gt; Will <span style='font-style: italic;'>install</span> 1 system package:</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #00BBBB;'>libpq-dev</span>  <span style='color: #555555;'>- </span><span style='color: #0000BB;'>RPostgres</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Getting 12 pkgs with unknown sizes</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>blob</span> 1.2.4 (x86_64-pc-linux-gnu-ubuntu-22.04) (45.94 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>generics</span> 0.1.3 (x86_64-pc-linux-gnu-ubuntu-22.04) (76.24 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>hms</span> 1.1.3 (x86_64-pc-linux-gnu-ubuntu-22.04) (98.35 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>RPostgres</span> 1.4.5 (x86_64-pc-linux-gnu-ubuntu-22.04) (455.11 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>bit64</span> 4.0.5 (x86_64-pc-linux-gnu-ubuntu-22.04) (475.41 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>pkgconfig</span> 2.0.3 (x86_64-pc-linux-gnu-ubuntu-22.04) (17.58 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>timechange</span> 0.2.0 (x86_64-pc-linux-gnu-ubuntu-22.04) (169.26 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>DBI</span> 1.1.3 (x86_64-pc-linux-gnu-ubuntu-22.04) (759.31 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>withr</span> 2.5.0 (x86_64-pc-linux-gnu-ubuntu-22.04) (228.73 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>bit</span> 4.0.5 (x86_64-pc-linux-gnu-ubuntu-22.04) (1.13 MB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>lubridate</span> 1.9.2 (x86_64-pc-linux-gnu-ubuntu-22.04) (980.37 kB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Got <span style='color: #0000BB;'>Rcpp</span> 1.0.11 (x86_64-pc-linux-gnu-ubuntu-22.04) (2.15 MB)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Installing system requirements</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Executing `sh -c apt-get -y update`</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Executing `sh -c apt-get -y install libpq-dev`</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>DBI</span> 1.1.3  <span style='color: #9E9E9E;'>(1.1s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>RPostgres</span> 1.4.5  <span style='color: #9E9E9E;'>(1.1s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>Rcpp</span> 1.0.11  <span style='color: #9E9E9E;'>(1.2s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>bit</span> 4.0.5  <span style='color: #9E9E9E;'>(1.2s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>bit64</span> 4.0.5  <span style='color: #9E9E9E;'>(126ms)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>blob</span> 1.2.4  <span style='color: #9E9E9E;'>(86ms)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>generics</span> 0.1.3  <span style='color: #9E9E9E;'>(83ms)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>hms</span> 1.1.3  <span style='color: #9E9E9E;'>(59ms)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>lubridate</span> 1.9.2  <span style='color: #9E9E9E;'>(1.1s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>pkgconfig</span> 2.0.3  <span style='color: #9E9E9E;'>(1.1s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>timechange</span> 0.2.0  <span style='color: #9E9E9E;'>(63ms)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>withr</span> 2.5.0  <span style='color: #9E9E9E;'>(1.1s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> 1 pkg + 16 deps: kept 5, added 12, dld 12 (6.58 MB) <span style='color: #B2B2B2;'>[17.1s]</span></span></span>
<span></span></code></pre>
</div>
<h3 id="running-r-as-a-regular-user">Running R as a regular user
</h3>
<p>If you don&rsquo;t want to use R as the superuser, but you can set up <code>sudo</code> without a password, that works as well. pak will detect the password-less <code>sudo</code> capability, and use it to install system packages, as needed.</p>
<p>If you run R as a regular (not root) user, and password-less <code>sudo</code> is not available, then pak will print the system requirements, but it will not try to install or update them.</p>
<p>If you are compiling R packages from source, and they need to link to system libraries, then their installation will probably fail, until you install these system packages.</p>
<p>If you are installing binary R packages (e.g. from <a href="https://packagemanager.posit.co/client/#/" target="_blank" rel="noopener">P3M</a>
), then the installation typically succeeds, but you won&rsquo;t be able to load these packages into R, until you install the required system packages.</p>
<p>To demonstrate this, let&rsquo;s remove the system package for the PostgreSQL client library:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'><a href='https://rdrr.io/r/base/system.html'>system</a></span><span class='o'>(</span><span class='s'>"apt-get remove -y libpq5"</span><span class='o'>)</span></span></code></pre>
</div>
<p>If now we (re)install the binary RPostgres R package, the installation will succeed, but then <a href="https://rdrr.io/r/base/library.html" target="_blank" rel="noopener"><code>library()</code></a>
 fails because of the missing system package. (We will fix the broken R package below.)</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Loading metadata database<span style='color: #00BB00;'>v</span> Loading metadata database ... done</span></span>
<span><span class='c'>#&gt;  </span></span>
<span><span class='c'>#&gt; &gt; Will <span style='font-style: italic;'>install</span> 1 package.</span></span>
<span><span class='c'>#&gt; &gt; Will <span style='font-style: italic;'>download</span> 1 package with unknown size.</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #0000BB;'>RPostgres</span>   1.4.5 [dl]<span style='color: #555555;'> + </span><span style='color: #BB0000;'>x</span><span style='color: #00BBBB;'> libpq-dev</span></span></span>
<span><span class='c'>#&gt; <span style='color: #BB0000;'>x</span> Missing 1 system package. You'll probably need to install it manually:</span></span>
<span><span class='c'>#&gt; <span style='color: #555555;'>+ </span><span style='color: #00BBBB;'>libpq-dev</span>  <span style='color: #555555;'>- </span><span style='color: #0000BB;'>RPostgres</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Getting 1 pkg with unknown size</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Cached copy of <span style='color: #0000BB;'>RPostgres</span> 1.4.5 (x86_64-pc-linux-gnu-ubuntu-22.04) is the latest build</span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> Installed <span style='color: #0000BB;'>RPostgres</span> 1.4.5  <span style='color: #9E9E9E;'>(1.1s)</span></span></span>
<span><span class='c'>#&gt; <span style='color: #00BB00;'>v</span> 1 pkg + 16 deps: kept 16, added 1 <span style='color: #B2B2B2;'>[5.7s]</span></span></span>
<span></span></code></pre>
</div>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://rpostgres.r-dbi.org'>RPostgres</a></span><span class='o'>)</span></span>
<span><span class='c'>#&gt; Error: package or namespace load failed for 'RPostgres' in dyn.load(file, DLLpath = DLLpath, ...):</span></span>
<span><span class='c'>#&gt;  unable to load shared object '/root/R/x86_64-pc-linux-gnu-library/4.3/RPostgres/libs/RPostgres.so':</span></span>
<span><span class='c'>#&gt;   libpq.so.5: cannot open shared object file: No such file or directory</span></span>
<span><span class='c'>#&gt; Execution halted</span></span>
<span></span></code></pre>
</div>
<h2 id="opting-out">Opting out
</h2>
<p>If you don&rsquo;t want pak to install system packages for you, set the <code>PKG_SYSREQS</code> environment variable to <code>false</code>, or the <code>pkg.sysreqs</code> option to <code>FALSE</code>. See the complete list of configuration options in the <a href="https://pak.r-lib.org/reference/pak-config.html" target="_blank" rel="noopener"><code>config?pak</code></a>
 manual page.</p>
<h2 id="system-requirements-queries">System requirements queries
</h2>
<p>pak 0.6.0 also has a number of functions to query system requirements and system packages. The <a href="https://pak.r-lib.org/reference/pkg_sysreqs.html" target="_blank" rel="noopener"><code>pak::pkg_sysreqs()</code></a>
 function is similar to <a href="https://pak.r-lib.org/reference/pkg_deps.html" target="_blank" rel="noopener"><code>pak::pkg_deps()</code></a>
 but in addition to looking up package dependencies, it also looks up system dependencies, and only reports the latter:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/pkg_sysreqs.html'>pkg_sysreqs</a></span><span class='o'>(</span><span class='nf'><a href='https://rdrr.io/r/base/c.html'>c</a></span><span class='o'>(</span><span class='s'>"curl"</span>, <span class='s'>"r-lib/xml2"</span>, <span class='s'>"devtools"</span>, <span class='s'>"CHRONOS"</span><span class='o'>)</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Loading metadata database<span style='color: #00BB00;'>v</span> Loading metadata database ... done</span></span>
<span><span class='c'>#&gt; -- Install scripts --------------------------------------------- Ubuntu 22.04 --</span></span>
<span><span class='c'>#&gt; apt-get -y update</span></span>
<span><span class='c'>#&gt; apt-get -y install libcurl4-openssl-dev libssl-dev git make libgit2-dev \</span></span>
<span><span class='c'>#&gt;   zlib1g-dev pandoc libfreetype6-dev libjpeg-dev libpng-dev libtiff-dev \</span></span>
<span><span class='c'>#&gt;   libicu-dev libfontconfig1-dev libfribidi-dev libharfbuzz-dev libxml2-dev \</span></span>
<span><span class='c'>#&gt;   libglpk-dev libgmp3-dev default-jdk</span></span>
<span><span class='c'>#&gt; R CMD javareconf</span></span>
<span><span class='c'>#&gt; R CMD javareconf</span></span>
<span><span class='c'>#&gt; </span></span>
<span><span class='c'>#&gt; -- Packages and their system dependencies --------------------------------------</span></span>
<span><span class='c'>#&gt; CHRONOS     -- default-jdk, pandoc</span></span>
<span><span class='c'>#&gt; credentials -- git</span></span>
<span><span class='c'>#&gt; curl        -- libcurl4-openssl-dev, libssl-dev</span></span>
<span><span class='c'>#&gt; fs          -- make</span></span>
<span><span class='c'>#&gt; gert        -- libgit2-dev</span></span>
<span><span class='c'>#&gt; gitcreds    -- git</span></span>
<span><span class='c'>#&gt; httpuv      -- make, zlib1g-dev</span></span>
<span><span class='c'>#&gt; igraph      -- libglpk-dev, libgmp3-dev, libxml2-dev</span></span>
<span><span class='c'>#&gt; knitr       -- pandoc</span></span>
<span><span class='c'>#&gt; openssl     -- libssl-dev</span></span>
<span><span class='c'>#&gt; pkgdown     -- pandoc</span></span>
<span><span class='c'>#&gt; png         -- libpng-dev</span></span>
<span><span class='c'>#&gt; ragg        -- libfreetype6-dev, libjpeg-dev, libpng-dev, libtiff-dev</span></span>
<span><span class='c'>#&gt; RCurl       -- libcurl4-openssl-dev, make</span></span>
<span><span class='c'>#&gt; remotes     -- git</span></span>
<span><span class='c'>#&gt; rJava       -- default-jdk, make</span></span>
<span><span class='c'>#&gt; rmarkdown   -- pandoc</span></span>
<span><span class='c'>#&gt; sass        -- make</span></span>
<span><span class='c'>#&gt; stringi     -- libicu-dev</span></span>
<span><span class='c'>#&gt; systemfonts -- libfontconfig1-dev, libfreetype6-dev</span></span>
<span><span class='c'>#&gt; textshaping -- libfreetype6-dev, libfribidi-dev, libharfbuzz-dev</span></span>
<span><span class='c'>#&gt; XML         -- libxml2-dev</span></span>
<span><span class='c'>#&gt; xml2        -- libxml2-dev</span></span>
<span></span></code></pre>
</div>
<p>See the manual of <a href="https://pak.r-lib.org/reference/pkg_sysreqs.html" target="_blank" rel="noopener"><code>pak::pkg_sysreqs()</code></a>
 to learn how to programmatically extract information from its return value.</p>
<p><a href="https://pak.r-lib.org/reference/sysreqs_check_installed.html" target="_blank" rel="noopener"><code>pak::sysreqs_check_installed()</code></a>
 is a handy function that checks if all system requirements are installed for some or all R packages in your library. This should report our broken RPostgres package:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/sysreqs_check_installed.html'>sysreqs_check_installed</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; system package installed required by</span></span>
<span><span class='c'>#&gt; -------------- --        -----------</span></span>
<span><span class='c'>#&gt; libpq-dev      <span style='color: #BB0000;'>x</span>         RPostgres</span></span>
<span></span></code></pre>
</div>
<p><a href="https://pak.r-lib.org/reference/sysreqs_check_installed.html" target="_blank" rel="noopener"><code>pak::sysreqs_fix_installed()</code></a>
 goes one step further and also tries to install the missing system requirements:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='nf'>pak</span><span class='nf'>::</span><span class='nf'><a href='http://pak.r-lib.org/reference/sysreqs_check_installed.html'>sysreqs_fix_installed</a></span><span class='o'>(</span><span class='o'>)</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Need to install 1 system package.</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Installing system requirements</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Executing `sh -c apt-get -y update`</span></span>
<span><span class='c'>#&gt; <span style='color: #00BBBB;'>i</span> Executing `sh -c apt-get -y install libpq-dev`</span></span>
<span></span></code></pre>
</div>
<p>Now we can load RPostgres again:</p>
<div class="highlight">
<pre class='chroma'><code class='language-r' data-lang='r'><span><span class='kr'><a href='https://rdrr.io/r/base/library.html'>library</a></span><span class='o'>(</span><span class='nv'><a href='https://rpostgres.r-dbi.org'>RPostgres</a></span><span class='o'>)</span></span>
<span></span></code></pre>
</div>
<h2 id="configuration">Configuration
</h2>
<p>There are several pak configuration options you can use to adjust how system requirements are handled. See the complete list in the <a href="https://pak.r-lib.org/reference/pak-config.html" target="_blank" rel="noopener"><code>config?pak</code></a>
 manual page.</p>
<h2 id="other-related-pak-functions">Other related pak functions
</h2>
<ul>
<li><a href="https://pak.r-lib.org/reference/sysreqs_db_list.html" target="_blank" rel="noopener"><code>pak::sysreqs_db_list()</code></a>
, <code>pak::sysreqs_dbmatch()</code> and <a href="https://pak.r-lib.org/reference/sysreqs_db_update.html" target="_blank" rel="noopener"><code>pak::sysreqs_db_update()</code></a>
 list, query and update the built-in system requirements database.</li>
<li><a href="https://pak.r-lib.org/reference/sysreqs_list_system_packages.html" target="_blank" rel="noopener"><code>pak::sysreqs_list_system_packages()</code></a>
 lists system packages, including virtual packages and the features they provide.</li>
</ul>
<h2 id="more-information">More information
</h2>
<ul>
<li><a href="https://pak.r-lib.org/" target="_blank" rel="noopener">pak documentation</a>
</li>
<li><a href="https://pak.r-lib.org/reference/sysreqs.html" target="_blank" rel="noopener">System requirements manual page</a>
</li>
<li><a href="https://github.com/rstudio/r-system-requirements" target="_blank" rel="noopener">System requirements database</a>
</li>
</ul>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to all those who have contributed to pak, or one of its workhorse packages since the v0.5.1 release:</p>
<p><a href="https://github.com/alexpate30" target="_blank" rel="noopener">@alexpate30</a>
, <a href="https://github.com/averissimo" target="_blank" rel="noopener">@averissimo</a>
, <a href="https://github.com/ArnaudKunzi" target="_blank" rel="noopener">@ArnaudKunzi</a>
, <a href="https://github.com/billdenney" target="_blank" rel="noopener">@billdenney</a>
, <a href="https://github.com/Darxor" target="_blank" rel="noopener">@Darxor</a>
, <a href="https://github.com/drmowinckels" target="_blank" rel="noopener">@drmowinckels</a>
, <a href="https://github.com/Fan-iX" target="_blank" rel="noopener">@Fan-iX</a>
, <a href="https://github.com/gongyh" target="_blank" rel="noopener">@gongyh</a>
, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>
, <a href="https://github.com/idavydov" target="_blank" rel="noopener">@idavydov</a>
, <a href="https://github.com/jefferis" target="_blank" rel="noopener">@jefferis</a>
, <a href="https://github.com/joan-yanqiong" target="_blank" rel="noopener">@joan-yanqiong</a>
, <a href="https://github.com/kevinushey" target="_blank" rel="noopener">@kevinushey</a>
, <a href="https://github.com/kkmann" target="_blank" rel="noopener">@kkmann</a>
, <a href="https://github.com/klmr" target="_blank" rel="noopener">@klmr</a>
, <a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>
, <a href="https://github.com/lgaborini" target="_blank" rel="noopener">@lgaborini</a>
, <a href="https://github.com/maelle" target="_blank" rel="noopener">@maelle</a>
, <a href="https://github.com/maxheld83" target="_blank" rel="noopener">@maxheld83</a>
, <a href="https://github.com/maximsmol" target="_blank" rel="noopener">@maximsmol</a>
, <a href="https://github.com/michaelmayer2" target="_blank" rel="noopener">@michaelmayer2</a>
, <a href="https://github.com/mine-cetinkaya-rundel" target="_blank" rel="noopener">@mine-cetinkaya-rundel</a>
, <a href="https://github.com/olivroy" target="_blank" rel="noopener">@olivroy</a>
, <a href="https://github.com/pascalgulikers" target="_blank" rel="noopener">@pascalgulikers</a>
, <a href="https://github.com/pawelru" target="_blank" rel="noopener">@pawelru</a>
, <a href="https://github.com/royfrancis" target="_blank" rel="noopener">@royfrancis</a>
, <a href="https://github.com/tanho63" target="_blank" rel="noopener">@tanho63</a>
, <a href="https://github.com/thomasyu888" target="_blank" rel="noopener">@thomasyu888</a>
, <a href="https://github.com/vincent-hanlon" target="_blank" rel="noopener">@vincent-hanlon</a>
, and <a href="https://github.com/VincentGuyader" target="_blank" rel="noopener">@VincentGuyader</a>
.</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2023/pak-0-6-0/thumbnail-wd.jpg" length="125618" type="image/jpeg" />
    </item>
    <item>
      <title>webR 0.2.0 has been released</title>
      <link>https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/</link>
      <pubDate>Wed, 16 Aug 2023 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/</guid>
      <dc:creator>George Stagg</dc:creator><description><![CDATA[<!--
TODO:
* [x] Look over / edit the post's title in the yaml
* [x] Edit (or delete) the description; note this appears in the Twitter card
* [x] Pick category and tags (see existing with [`hugodown::tidy_show_meta()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html))
* [x] Find photo & update yaml metadata
* [x] Create `thumbnail-sq.jpg`; height and width should be equal
* [x] Create `thumbnail-wd.jpg`; width should be >5x height
* [x] [`hugodown::use_tidy_thumbnails()`](https://rdrr.io/pkg/hugodown/man/use_tidy_post.html)
* [x] Add intro sentence, e.g. the standard tagline for the package
* [x] [`usethis::use_tidy_thanks()`](https://usethis.r-lib.org/reference/use_tidy_thanks.html)
* [x] Release webR 0.2.0
* [] Update all links from /0.2.0-rc.1 to /0.2.0
* [x] Update webr-repo packages
* [x] Update webr-repo dashboard
-->
<!-- Initialise webR in the page -->


<!-- Add webr engine for knit -->
<div class="highlight">
</div>
<p>We&rsquo;re absolutely thrilled to announce the release of <a href="https://docs.r-wasm.org/webr/v0.2.0/" target="_blank" rel="noopener">webR</a>
 0.2.0! This release gathers together many updates and improvements to webR over the last few months, including improvements to the HTML canvas graphics device, support for Cairo-based bitmap graphics, accessibility and internationalisation improvements, additional Wasm R package support (including Shiny), a new webR REPL app, and various updates to the webR developer API.</p>
<p>This blog post will take a deep dive through the major breaking changes and new features available in webR 0.2.0. I also plan to record and release a series of companion videos discussing the new release, so keep an eye out if you&rsquo;re someone who prefers watching and listening over reading long-form articles. I&rsquo;ll update this post with all the links once they&rsquo;re available.</p>
<h2 id="webassembly-and-webr">WebAssembly and webR
</h2>
<p>My previous <a href="https://www.tidyverse.org/blog/2023/03/webr-0-1-0/" target="_blank" rel="noopener">webR release blog post</a>
 goes into detail about what WebAssembly is, why people are excited about it, and how it relates to the R community and ecosystem in general through webR. I would recommend it as a good place to start, if the project is new to you<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>A short explanation is that WebAssembly (also known as Wasm) allows software that&rsquo;s normally compiled for a specific computer system to instead run anywhere, including in web browsers. Wasm is the technology that powers <a href="https://pyodide.org" target="_blank" rel="noopener">Pyodide</a>
 (used by <a href="https://shiny.rstudio.com/py/docs/shinylive.html" target="_blank" rel="noopener">Shinylive for Python</a>
) and webR brings this technology to the R world. Using webR it is possible to run R code directly in a web browser<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, without the need for the traditional supporting R server to execute the code.</p>
<p>Running R code directly in a browser opens the door for many new and exciting uses for R on the web. Applications that I&rsquo;m personally excited in seeing developed are,</p>
<ul>
<li>Live and interactive R code and graphics in documents &amp; presentations,</li>
<li>Tactile educational content for R, with examples that can be remixed on-the-fly by learners,</li>
<li>Reproducible statistics through containerisation and notebook-style literate programming.</li>
</ul>
<p>Even in these early days, some of this is already being provided by development of downstream projects such as James Balamuta&rsquo;s <a href="https://github.com/coatless/quarto-webr" target="_blank" rel="noopener">quarto-webr</a>
 extension, allowing Quarto users to easily embed interactive R code blocks in their documents.</p>
<h3 id="interactive-code-blocks">Interactive code blocks
</h3>
<p>One of my favourite demonstrations of what webR can do is interactive code blocks for R code. After a short loading period while the webR binary is downloaded, a <strong>Run code</strong> button will be enabled below. Using examples like this, R code can be immediately edited and executed &ndash; feel free to experiment! Click the &ldquo;Run code&rdquo; button to see the resulting box plot, change the colour from <code>mediumseagreen</code> to <code>red</code> and run the code again.</p>
<div class="highlight">


</div>
<p>It&rsquo;s easy to see the potential teaching benefit examples like this could bring to educational content or R package documentation.</p>
<h2 id="the-webr-repl-app">The webR REPL app
</h2>
<p>WebR can be loaded into a web page to be used as a part of a wider web application, and ships with a demo application that does just that. The webR REPL app<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> provides a simple R environment directly in your web browser. The app can be accessed at <a href="https://webr.r-wasm.org/v0.2.0/" target="_blank" rel="noopener">https://webr.r-wasm.org/v0.2.0/</a>
 and includes sections for R console input/output, code editing, file management, and graphics device output.</p>
<p>With the webR REPL app, a casual user could get up and running with R in seconds, without having to install any software on their machine. It is entirely feasible that they could perform the basics of data science entirely within their web browser!</p>
<p>Other than interactive code blocks, like in the example earlier, the webR REPL app is perhaps the first thing that users new to webR will interact with. For this reason, we have spent some time working to improve the technical implementation and user experience of using the app. The app has been completely rewritten in the React web framework, replacing the older jQuery library. This allows for better component code organisation and more rapid development of features and updates.</p>
<p><a href="repl.png"><img alt="A screenshot the webR REPL app. The code to generate a ggplot, along with its output, is shown in the app." width="95%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/repl.png"></a></p>
<h3 id="code-editor">Code editor
</h3>
<p>The app now comes with a tabbed code editor, allowing for easier editing and execution of R code. The editor integrates with the webR virtual filesystem (VFS), meaning that multiple R scripts can be opened, edited, and saved and they will be available to the running Wasm R process.</p>
<p>The editor pane is built upon the excellent <a href="https://codemirror.net" target="_blank" rel="noopener">CodeMirror</a>
 text editor, which provides most of the component&rsquo;s functionality. CodeMirror provides built-in support for syntax highlighting of R code, which is enabled by default when R source files are displayed.</p>
<p>The editor is integrated with the currently running R process and automatic code suggestions are shown as you type, provided by R&rsquo;s <a href="https://stat.ethz.ch/R-manual/R-devel/library/utils/html/rcompgen.html" target="_blank" rel="noopener">built in completion generator</a>
. The suggestions are context sensitive and are aware of package and function names, valid arguments, and even objects that exist in the global environment.</p>
<p><a href="completion.png"><img alt="A screenshot of the editor component showing code completion results. One of the suggestions is a data set available in the global environment." width="70%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/completion.png"></a></p>
<p>The running Wasm R process is also configured at initialisation to use the editor component as its display <a href="https://stat.ethz.ch/R-manual/R-devel/library/base/html/file.show.html" target="_blank" rel="noopener">pager mechanism</a>
. With this configuration in place running commands such as <a href="https://rdrr.io/r/stats/Normal.html" target="_blank" rel="noopener"><code>?rnorm</code></a>
 in the app automatically opens a new read-only tab in the editor displaying R&rsquo;s built-in documentation.</p>
<p><a href="documentation.png"><img alt="A screenshot of the editor component showing built-in R documentation" width="80%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/documentation.png"></a></p>
<h3 id="plotting-pane">Plotting pane
</h3>
<p>The plotting pane has been updated to take advantage of improvements in webR&rsquo;s HTML canvas graphics device, set as the default device as part of initialisation. In particular, multiple plots are now supported and older plots can be directly accessed using the previous and next buttons in the plotting toolbar. You can try this out with R&rsquo;s built in graphics demo, by running <code>demo(graphics)</code> and/or <code>demo(persp)</code>.</p>
<p><a href="plotting.png"><img alt="A screenshot of the plot pane showing a built-in R graphics demo" width="75%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/plotting.png"></a></p>
<h3 id="files-pane">Files pane
</h3>
<p>The files pane has been completely redesigned, removing its dependency on jQuery and instead making use of the <a href="https://www.npmjs.com/package/react-accessible-treeview" target="_blank" rel="noopener">react-accessible-treeview</a>
 package. As well as a technical improvement, this change means that interacting with the webR filesystem should be more usable to those with web accessibility requirements. We feel it&rsquo;s important that, where possible, everybody is able to use our software.</p>
<p><a href="files.png"><img alt="A screenshot of the files pane showing the path /home/web_user/plot_random_numbers.R" width="90%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/files.png"></a></p>
<p>Additional buttons have also been added to this pane, allowing users to easily manipulate the virtual file system visible to the running Wasm R process. New files and directories can be created or deleted, and text-based files can be directly opened and modified in the editor pane, removing the need to download, edit and then re-upload files.</p>
<h3 id="console-pane">Console pane
</h3>
<p>The R console component shown in the lower left portion of the app is powered by the wonderful <a href="https://xtermjs.org" target="_blank" rel="noopener">xterm.js</a>
 software, which provides a high performance terminal emulator on the web. R output looks at its best when running in this kind of environment, so that <a href="https://en.wikipedia.org/wiki/ANSI_escape_code" target="_blank" rel="noopener">ANSI escape codes</a>
 can be used to provide a much smoother console experience incorporating cursor placement, box drawing characters, bold text, terminal colours, and more.</p>
<p><a href="term.png"><img alt="An example of ANSI escape sequences in R console output while loading the tidyverse package." width="90%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/term.png"></a></p>
<p>An optional accessibility mode is provided by xterm.js so that terminal output is readable by screen reader software, such as <a href="https://support.apple.com/en-gb/guide/voiceover/welcome/mac" target="_blank" rel="noopener">macOS&rsquo;s VoiceOver</a>
. The webR REPL app now enables this mode by default to improve the accessibility of terminal output.</p>
<h2 id="html-canvas-graphics-device">HTML Canvas graphics device
</h2>
<p>The webR support package provides a custom <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
 graphics device that renders output using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" target="_blank" rel="noopener">Web Canvas API</a>
. When the graphics device is used, drawing commands from R are translated into Canvas API calls. The browser renders the graphics and the resulting image data is drawn to a HTML <code>&lt;canvas&gt;</code> element on the page.</p>
<p>With the release of webR 0.2.0, we have improved the performance and added new features to the HTML canvas graphics device.</p>
<h3 id="performance-improvements-with-offscreencanvas">Performance improvements with <code>OffscreenCanvas</code>
</h3>
<p>Using the Canvas API to draw graphics in a browser is elegant, but presents a problem. R is running via WebAssembly in a JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" target="_blank" rel="noopener">Web Worker</a>
 thread, but the <code>&lt;canvas&gt;</code> element the plot image data is written to is on the main thread, part of the web page <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction" target="_blank" rel="noopener">DOM</a>
. And, unfortunately, JavaScript Web Worker threads have no direct access to the DOM.</p>
<p>Previous releases of webR solve this problem in a rather naive way, it simply sends the Canvas API calls to the main thread to be executed there. This leads to a few issues,</p>
<ul>
<li>
<p>Canvas API calls are serialised as text to be sent to the main thread. Sufficiently complex plot text must therefore be quoted and escaped.</p>
</li>
<li>
<p>Each API call is sent in a separate message. For a complex plot this can be thousands of messages to dispatch and handle.</p>
</li>
<li>
<p>The messaging is one-way, results of useful methods like <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText" target="_blank" rel="noopener"><code>measureText()</code></a>
 cannot easily be retrieved.</p>
</li>
<li>
<p>Parsing and executing the API call on the main thread means using JavaScript&rsquo;s <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval" target="_blank" rel="noopener"><code>eval()</code></a>
 or <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function" target="_blank" rel="noopener"><code>Function()</code></a>
, leading to poor performance. These functions should also be avoided when possible in any case, for security reasons.</p>
</li>
</ul>
<p>Solid engineering efforts could be made to improve the situation, e.g. through batching API calls and better encoding, but there is a better way: the <a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas" target="_blank" rel="noopener"><code>OffscreenCanvas</code></a>
 interface. <code>OffscreenCanvas</code> is designed to solve this exact problem of rendering graphics off-screen, such as in a worker thread. With <code>OffscreenCanvas</code> the Canvas API calls can all be executed on the worker thread, and only a single message containing the completed image data transferred to the main thread when rendering is complete. It is an efficient and technically satisfying solution, except that when webR 0.1.1 was released <code>OffscreenCanvas</code> wasn&rsquo;t supported by the Safari web browser.</p>
<p>Today, on the other hand, <code>OffscreenCanvas</code> is <a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas#browser_compatibility" target="_blank" rel="noopener">supported</a>
 in all major desktop and mobile browsers. Safari has supported it since version 16.4, and so with webR 0.2.0 we have rewritten the <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
 graphics device to take full advantage of the <code>OffscreenCanvas</code> interface. This has led to a significant performance improvement, particularly when creating plots containing many points. The two videos below show the same plot rendered in webR 0.1.1 and 0.2.0, the difference is not just visible, but an order of magnitude faster.</p>
<video controls loop width="100%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/plot.mp4" style="border: 2px solid #CCC;">
<source src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/plot.mp4">
</video>
<div style="text-align: center; font-weight: bold;">
<p>A performance comparison plotting 300000 points in webR 0.1.1 and 0.2.0.</p>
</div>
<p>A potential downside is that users of less up-to-date browsers without <code>OffscreenCanvas</code> support won&rsquo;t be able to use the <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
 graphics device. Such users should instead make use of our additional updates to webR to support the traditional Cairo-based bitmap devices. The <a href="#built-in-bitmap-graphics-devices">built-in graphics devices section</a>
 discusses that in more detail.</p>
<h3 id="modern-text-rendering-and-internationalisation">Modern text rendering and internationalisation
</h3>
<p>With webR 0.1.1, the canvas graphics device had only minimal support for rendering text. The typeface was fixed, the font metrics were estimated with a heuristic, and Unicode characters outside the Basic Latin block often failed to render. It worked most of the time, but it was far from ideal. This area of software engineering is <a href="https://faultlore.com/blah/text-hates-you/" target="_blank" rel="noopener">suprisingly difficult</a>
 to get right, and even native installations of R can have <a href="https://www.tidyverse.org/blog/2021/02/modern-text-features/" target="_blank" rel="noopener">serious text rendering issues</a>
.</p>
<p>In comparison, web browser support for text rendering is excellent. Now that we use the <code>OffscreenCanvas</code> interface, we too can take advantage of the years of work behind browser&rsquo;s support for text on the web. The example below demonstrates several of the modern text rendering features now supported by <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
.</p>
<div class="highlight">


</div>
<p>Any system font available to the web browser can now be used<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>. As well as a nice-to-have, this also provides improved accessibility. For example, there are fonts designed specifically for use by readers with dyslexia and other similar reading barriers<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> that could be used for drawing text in plots.</p>
<p>Font metrics are now exact, using <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText" target="_blank" rel="noopener"><code>measureText()</code></a>
, rather than estimating the width and height of Latin glyphs using heuristics. This gives more accurate positioning of rendered text and improves the general quality of resulting plots.</p>
<p>Support for Unicode, font glyph fallback, complex ligatures, and right-to-left (RTL) text have all been improved. This vastly improves results when rendering text for international users, particularly for non-Latin RTL scripts such as the Arabic and Hebrew text in the example above.</p>
<p>Also, colour emoji can now be added to plots. 😃</p>
<h3 id="paths-and-winding-rules">Paths and winding rules
</h3>
<p>Additional support for the drawing and filling of paths and polygons, including with different <a href="https://oreillymedia.github.io/Using_SVG/extras/ch06-fill-rule.html" target="_blank" rel="noopener">winding rules</a>
, has been added to the webR canvas graphics device. An area where this new functionality makes a world of difference is plotting spatial features and maps. Previously broken R code for plotting maps with the <code>ggplot2</code> and <code>sf</code> packages now works well with webR 0.2.0.</p>
<p><a href="paths.png"><img alt="A screenshot of R plotting code testing paths with winding settings and map plotting. Output on the left for webR 0.1.1 is broken. Output on the right for webR 0.2.0 works correctly" width="95%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/paths.png"></a></p>
<h3 id="output-messages-from-the-canvas-graphics-device">Output messages from the canvas graphics device
</h3>
<p>As a result of the changes to the HTML canvas graphics device, the structure of output messages communicated to the main thread has been redesigned. This is a breaking change and existing webR applications will need to be updated to listen for the new output messaging format.</p>
<p><a href="https://docs.r-wasm.org/webr/latest/plotting.html" target="_blank" rel="noopener">A Plotting section</a>
 has been added to the webR documentation describing how plotting works with the <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
 device, and how to handle the output messages in your own web applications.</p>
<p>A <code>'canvas'</code> type output message with an <code>event</code> property of <code>'canvasNewPage'</code> indicates the start of a new plot,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="p">{</span> <span class="kr">type</span><span class="o">:</span> <span class="s1">&#39;canvas&#39;</span><span class="p">,</span> <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">event</span><span class="o">:</span> <span class="s1">&#39;canvasNewPage&#39;</span> <span class="p">}</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>An output message with an <code>event</code> property of <code>'canvasImage'</code> indicates that there is some graphics data ready to be drawn,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="p">{</span> <span class="kr">type</span><span class="o">:</span> <span class="s1">&#39;canvas&#39;</span><span class="p">,</span> <span class="nx">data</span><span class="o">:</span> <span class="p">{</span> <span class="nx">event</span><span class="o">:</span> <span class="s1">&#39;canvasImage&#39;</span><span class="p">,</span> <span class="nx">image</span>: <span class="kt">ImageBitmap</span> <span class="p">}</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>image</code> property in the message data contains a JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap" target="_blank" rel="noopener"><code>ImageBitmap</code></a>
 object. This can be drawn to a HTML <code>&lt;canvas&gt;</code> element using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage" target="_blank" rel="noopener"><code>drawImage()</code></a>
 method.</p>
<h2 id="built-in-bitmap-graphics-devices">Built-in bitmap graphics devices
</h2>
<p>Not all environments where webR could be running support plotting to a HTML <code>&lt;canvas&gt;</code> element. Older browsers may not support the required <code>OffscreenCanvas</code> interface, webR might be running server-side in Node.js, or webR might be running more traditional R code or packages that are unaware of the <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
 graphics device.</p>
<p>For supporting these use cases, with webR 0.2.0 the built-in bitmap graphics devices are now able to be used, writing their output to the webR VFS. This includes the <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>png()</code></a>
, <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>bmp()</code></a>
, <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>jpeg()</code></a>
, <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>tiff()</code></a>
 devices, and potentially others implemented using the Cairo graphics library.</p>
<p>In the example below, webR is loaded into a JavaScript environment and plotting is done using the built-in <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>png()</code></a>
 graphics device. The resulting image is written to the virtual filesystem and its contents can then be obtained using webR&rsquo;s <a href="https://docs.r-wasm.org/webr/latest/api/js/classes/WebR.WebR.html#fs" target="_blank" rel="noopener"><code>FS</code></a>
 interface, designed to be similar to <a href="https://emscripten.org/docs/api_reference/Filesystem-API.html" target="_blank" rel="noopener">Emscripten&rsquo;s filesystem API</a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;webr&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">webR</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">WebR</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">init</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalRVoid</span><span class="p">(</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  png(&#39;/tmp/Rplot.png&#39;, width = 800, height = 800, res = 144)
</span></span></span><span class="line"><span class="cl"><span class="sb">  hist(rnorm(1000))
</span></span></span><span class="line"><span class="cl"><span class="sb">  dev.off()
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">plotImageData</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">FS</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="s1">&#39;/tmp/Rplot.png&#39;</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The image data is contained in the <code>plotImageData</code> variable as a JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array" target="_blank" rel="noopener"><code>UInt8Array</code></a>
. Once obtained from the VFS, the image can be served to the end user as a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" target="_blank" rel="noopener"><code>Blob</code></a>
 file download, displayed on a web page, or if running webR server-side returned over the network.</p>
<h3 id="text-rendering-and-font-support">Text rendering and font support
</h3>
<p>As with the <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
 improvements described in the previous section, we feel it is important that the built in R graphics devices provides a high level of support for text rendering in webR. Here, however, the approach is different. The built-in graphics devices renders image data entirely within the WebAssembly environment, so we can no longer rely on the web browser for high quality text!</p>
<p>The built-in graphics devices are powered by the Cairo graphics library, which can now optionally be compiled for Wasm as part of the webR build process. In addition, when enabled various other libraries are compiled for Wasm to improve the quality of text rendering in Cairo,</p>
<ul>
<li><a href="https://pango.gnome.org" target="_blank" rel="noopener">pango</a>
</li>
<li><a href="http://fribidi.org" target="_blank" rel="noopener">fribidi</a>
</li>
<li><a href="https://harfbuzz.github.io" target="_blank" rel="noopener">harfbuzz</a>
</li>
<li><a href="https://freetype.org" target="_blank" rel="noopener">freetype</a>
</li>
<li><a href="https://www.freedesktop.org/wiki/Software/fontconfig/" target="_blank" rel="noopener">fontconfig</a>
</li>
</ul>
<p>Public releases of webR distributed via GitHub and CDN will be built with these libraries all enabled and included.</p>
<h4 id="font-files-on-the-vfs">Font files on the VFS
</h4>
<p>When plotting with the built-in bitmap graphics devices, fonts must be accessible to the Cairo library through the webR VFS. A minimal selection of <a href="https://fonts.google.com/noto" target="_blank" rel="noopener">Google&rsquo;s Noto fonts</a>
 are bundled with webR when Cairo graphics is enabled.</p>
<p>The fontconfig library is also configured to search the VFS directory <code>/home/web_user/fonts</code> for additional fonts. Users who wish to use custom fonts, or alternative writing systems, may do so by uploading font files to this directory. In the case of international scripts or non-Latin Unicode such as emoji, fontconfig will automatically use font fallback to select reasonable fonts containing the required glyphs.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">png</span><span class="p">(</span><span class="n">width</span> <span class="o">=</span> <span class="m">1200</span><span class="p">,</span> <span class="n">height</span> <span class="o">=</span> <span class="m">800</span><span class="p">,</span> <span class="n">res</span> <span class="o">=</span> <span class="m">180</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">plot</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">rnorm</span><span class="p">(</span><span class="m">1000</span><span class="p">),</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1000</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">col</span> <span class="o">=</span> <span class="nf">rgb</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0.5</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">xlim</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">-5</span><span class="p">,</span> <span class="m">5</span><span class="p">),</span> <span class="n">ylim</span> <span class="o">=</span> <span class="nf">c</span><span class="p">(</span><span class="m">-5</span><span class="p">,</span> <span class="m">5</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="n">main</span> <span class="o">=</span> <span class="s">&#34;This is the title 🚀&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">xlab</span> <span class="o">=</span> <span class="s">&#34;This is the x label&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">ylab</span> <span class="o">=</span> <span class="s">&#34;This is the y label&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">text</span><span class="p">(</span><span class="m">-3.5</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="s">&#34;This is English&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">text</span><span class="p">(</span><span class="m">-3.5</span><span class="p">,</span> <span class="m">-4</span><span class="p">,</span> <span class="s">&#34;هذا مكتوب باللغة العربية&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">text</span><span class="p">(</span><span class="m">3.5</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="s">&#34;これは日本語です&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">text</span><span class="p">(</span><span class="m">3.5</span><span class="p">,</span> <span class="m">-4</span><span class="p">,</span> <span class="s">&#34;זה כתוב בעברית&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">dev.off</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is essentially the same example as in the previous section, demonstrating a selection of advanced font functionality. In this example we are rendering a PNG file using the built-in <a href="https://rdrr.io/r/grDevices/png.html" target="_blank" rel="noopener"><code>png()</code></a>
 graphics device. We can see that by uploading appropriate fonts to the VFS, the same set of advanced text rendering features that are provided by the browser can also be used with R&rsquo;s built-in bitmap graphics devices.</p>
<p><a href="textplot.png"><img alt="A screenshot showing the output of the above plotting code is shown on the left. The additional fonts uploaded to the VFS are listed on the right." width="100%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/textplot.png"></a></p>
<h2 id="lazy-virtual-filesystem">Lazy virtual filesystem
</h2>
<p>All of the additional features I&rsquo;ve written about so far come with a price: increased Wasm binary and data download size. Consider the fonts in the previous section - each font file bundled with webR is going to increase the total size of the default webR filesystem by around 500KB.</p>
<p>This is a high price to pay in time and bandwidth when not every user is going to need every feature. A similar principle also applies to other files included with R by default. It&rsquo;s nice that all the default R documentation, examples, and datasets are available on the VFS, but we don&rsquo;t necessarily need those files downloaded every time to every client machine.</p>
<p>With webR 0.2.0 a &ldquo;lazy&rdquo; virtual filesystem mechanism, powered by <a href="https://emscripten.org/docs/porting/files/Synchronous-Virtual-XHR-Backed-File-System-Usage.html" target="_blank" rel="noopener">a feature of Emscripten&rsquo;s FS API</a>
, is introduced. With this, only the files required to launch R and use the default packages are downloaded at initialisation time. Additional files provided on the VFS are still available for use, but they are only downloaded from the remote server when they are requested in some way by the running Wasm R process.</p>
<p>With the introduction of the lazy virtual filesystem, along with other efficiency improvements, the initial download size for webR is now much smaller, a great improvement.</p>
<table>
  <thead>
      <tr>
          <th>Component</th>
          <th>0.1.1</th>
          <th>0.2.0</th>
          <th>(% of previous)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>R.bin.data</code></td>
          <td>25.3MB</td>
          <td>5.2MB</td>
          <td>20.6%</td>
      </tr>
      <tr>
          <td><code>R.bin.wasm</code></td>
          <td>12.8MB</td>
          <td>1.7MB</td>
          <td>7.5%</td>
      </tr>
      <tr>
          <td><strong>Total for the webR REPL app</strong></td>
          <td>40.2MB</td>
          <td>9.5MB</td>
          <td>23.6%</td>
      </tr>
  </tbody>
</table>
<h2 id="r-packages">R packages
</h2>
<p>Since initial release, webR has supported loading R packages by first installing them to the Emscripten VFS using the helper function <a href="https://docs.r-wasm.org/webr/latest/api/r.html#install-one-or-more-packages-from-a-webr-binary-package-repo" target="_blank" rel="noopener"><code>webr::install()</code></a>
 or by manually placing R packages in the VFS at <code>/usr/lib/R/library</code>. We find that pure R packages usually work well, but R packages with underlying C (or Fortran, or otherwise&hellip;) code must be compiled from source for Wasm.</p>
<p>We host a public CRAN-like R package repository containing packages built for Wasm in this way, so that there exists a subset of useful and supported R packages that can be used with webR. The public repository is hosted at <a href="https://repo.r-wasm.org" target="_blank" rel="noopener">https://repo.r-wasm.org</a>
 and this repo URL is used by default when running <a href="https://docs.r-wasm.org/webr/latest/api/r.html#install-one-or-more-packages-from-a-webr-binary-package-repo" target="_blank" rel="noopener"><code>webr::install()</code></a>
 to install a Wasm R package.</p>
<p>It remains the case that building custom R packages for Wasm is not well documented, but we do hope to improve the situation over time as our package build infrastructure develops and matures. In the future, we plan to provide a Wasm R package build system as a set of Docker containers, so that users are able to build their own packages for webR using a container environment.</p>
<h3 id="webassembly-system-libraries-for-r-packages">WebAssembly system libraries for R packages
</h3>
<p>Many R packages require linking with system libraries to build and run. When building such R packages for WebAssembly, not only does the package code require compiling for Wasm, but also any system libraries that code depends on.</p>
<p>To expand support for R packages, webR 0.2.0 ships with <a href="https://github.com/r-wasm/webr/tree/main/libs/recipes" target="_blank" rel="noopener">additional recipes</a>
 to build system libraries from source for Wasm. The libraries consist of a selection of utility, database, graphics, text rendering, geometry, and geospatial support packages, with specific libraries chosen for their possibility to be compiled for Wasm as well as the number of R packages relying on them. I expect that the number of system libraries supported will continue to grow over time as we attempt to build more R packages for Wasm.</p>
<p>As of webR 0.1.1, <strong>219</strong> packages were available to install through our public Wasm R package repo. With the release of webR 0.2.0 and its additional system libraries, the number of available packages is now <strong>10324</strong> (approximately 51% of CRAN packages). Though, it should be noted that these packages have not been tested in detail. Here, &ldquo;available&rdquo; just means that the Emscripten compiler successfully built the R package for Wasm, along with its prerequisite packages.</p>
<h3 id="public-wasm-r-packages-dashboard">Public Wasm R packages dashboard
</h3>
<p>While available R packages can be listed using <a href="https://rdrr.io/r/utils/available.packages.html" target="_blank" rel="noopener"><code>available.packages()</code></a>
 with our CRAN-like Wasm R package repo, it&rsquo;s not the smoothest experience for users simply wanting to check if a given package is available. A dashboard has been added to the <a href="https://repo.r-wasm.org" target="_blank" rel="noopener">repo index page</a>
 which lists the available packages compiled for Wasm in an interactive table. The table also lists package dependencies, noting which prerequisite packages, if any, are still missing.</p>
<p><a href="repo.png"><img alt="A screenshot of the webR binary R package repository index page. A table of available R packages is shown, along with their prerequisites" width="95%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/repo.png"></a></p>
<p>It might be interesting to note that this dashboard itself is running under webR, through a fully client-side Shiny app.</p>
<h2 id="running-httpuv--shiny-under-webr">Running httpuv &amp; Shiny under webR
</h2>
<p>Using features new to webR 0.2.0, a <a href="https://github.com/r-wasm/httpuv" target="_blank" rel="noopener">httpuv webR package shim</a>
 has been created that provides the functionality usually provided by the <a href="https://cran.r-project.org/web/packages/httpuv/index.html" target="_blank" rel="noopener">httpuv</a>
 R package. The package enables R to handle HTTP and WebSocket traffic, and is a prerequisite for the R Shiny package.</p>
<p>The shim works by taking advantage of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" target="_blank" rel="noopener">JavaScript Service Worker API</a>
. Normally Service Workers are used to implement fast offline caching of web content, but they can also be used as a general network proxy. The httpuv shim makes use of a Service Worker to intercept network traffic from a running Shiny web client, and forward that traffic to be handled by an instance of webR.</p>
<p>From the Shiny server&rsquo;s point of view, it is communicating with the usual httpuv package using its R API. From the point of view of the Shiny web client, it is talking to a Shiny server over the network. Between the two, the JavaScript Service Worker and webR work together to act as a network proxy and handle the traffic entirely within the client<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>.</p>
<p><a href="httpuv.png"><img alt="A block diagram showing how the httpuv shim, webR worker thread, and Shiny work together. See the preceding diagram for an explanation of how the blocks interact" width="90%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/httpuv.png"></a></p>
<p>The httpuv shim package is still in the experimental stage, but it is currently available for testing and is included in our public webR package repository.</p>
<h3 id="an-example-shiny-app">An example shiny app
</h3>
<p><a href="shiny.png"><img alt="A screenshot of the webR Shiny demo. The shiny app is shown in the top section of the screenshot, an input slider and an output histogram plot. The lower section shows the normally server-side Shiny package console output when tracing is enabled." width="90%" src="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/shiny.png"></a></p>
<p>An example Shiny app, making use of the httpuv shim and running fully client-side, is available at <a href="https://shiny-standalone-webr-demo.netlify.app" target="_blank" rel="noopener">https://shiny-standalone-webr-demo.netlify.app</a>
.</p>
<p>Once the app has loaded in your browser, it&rsquo;s possible to confirm that the app is running entirely client-side by observing the Shiny server trace output at the bottom of the screen. You should even be able to disconnect completely from the internet and continue to use the app offline.</p>
<p>The source code for the demo, which includes some information describing how to set up a webR Shiny server in this way, can be found at <a href="https://github.com/georgestagg/shiny-standalone-webr-demo">georgestagg/shiny-standalone-webr-demo</a>. Note that this repository is targeted towards advanced web developers with prior experience of development with JavaScript Web Workers. It is intended as a demonstration of the technology, rather than a tutorial.</p>
<p>A coming-soon version of Shinylive for R will provide a much better user experience for getting fully client-side R Shiny apps up and running, without requiring advanced knowledge of JavaScript&rsquo;s Worker API. I believe Shinylive with webR integration will pave the way for providing a user-friendly method to build and deploy containerised R Shiny apps, running on WebAssembly.</p>
<h2 id="changes-to-the-webr-developer-api">Changes to the webR developer API
</h2>
<p>It&rsquo;s possible for webR to be used in isolation, but it&rsquo;s likely that developers will want to interface webR with other JavaScript frameworks and tools. The dynamism and interconnectivity of the web is one of its great strengths, and we&rsquo;d like the same to be true of webR. This section describes changes to webR&rsquo;s developer API, used to interact with the running R session from the JavaScript environment.</p>
<h3 id="performance-improvements-with-messagepack-protocol">Performance improvements with MessagePack protocol
</h3>
<p>When working to integrate webR into a wider application, at some point we will need to move data into the running R process, and later return results back to JavaScript. It&rsquo;s possible to move data into R by evaluating R code directly, but the webR library also provides <a href="https://docs.r-wasm.org/webr/latest/convert-js-to-r.html" target="_blank" rel="noopener">other ways to transfer raw data to R</a>
.</p>
<p>Consider the example below. Data is transferred from JavaScript into the running R process by binding <code>jsData</code> to an R variable in the global environment using <a href="https://docs.r-wasm.org/webr/latest/convert-js-to-r.html#binding-objects-to-an-r-environment" target="_blank" rel="noopener"><code>webR.objs.globalEnv.bind()</code></a>
. Next, some computation on the data is done, represented as evaluating the <code>do_analysis()</code> R function. Finally the result is returned back to JavaScript, first as a reference to an R object and then transferring the result data back to the JavaScript environment using <a href="https://docs.r-wasm.org/webr/latest/convert-r-to-js.html#serialising-r-objects" target="_blank" rel="noopener"><code>toJs()</code></a>
.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">jsData</span> <span class="o">=</span> <span class="p">[...</span> <span class="nx">some</span> <span class="nx">large</span> <span class="nx">JavaScript</span> <span class="nx">dataset</span> <span class="p">...];</span>
</span></span><span class="line"><span class="cl"><span class="kr">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">objs</span><span class="p">.</span><span class="nx">globalEnv</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="s1">&#39;data&#39;</span><span class="p">,</span> <span class="nx">jsData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ret</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="s2">&#34;do_analysis(data)&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">ret</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It&rsquo;s easy to see how this workflow could be useful as part of a wider application, enabling a complex data manipulation or a statistical modelling in R that would otherwise be awkward to perform directly in JavaScript.</p>
<p>Behind the scenes, we&rsquo;ve done work to ensure that data is transferred efficiently to and from the R environment, and in webR 0.2.0 the <a href="https://msgpack.org/index.html" target="_blank" rel="noopener">MessagePack</a>
 protocol is now used as the main way that data is serialised and transferred, replacing JSON encoding.</p>
<p>This change provides a significant performance improvement. <a href="https://github.com/r-wasm/webr/pull/204" target="_blank" rel="noopener">Initial testing</a>
 shows an order of magnitude speed boost when transferring large sets of data from the JavaScript environment into R. Thanks to <a href="https://github.com/r-wasm/webr/issues/203" target="_blank" rel="noopener">@jeroen</a>
 for prompting me to look into it!</p>
<h3 id="the-typing-of-r-object-references">The typing of R object references
</h3>
<p>When working with webR in TypeScript it is important to keep track of R object types. All references to R objects are instances of the <a href="https://docs.r-wasm.org/webr/latest/objects.html" target="_blank" rel="noopener"><code>RObject</code></a>
 class, and various subclasses implement specific features for each fundamental R data type.</p>
<p>In this example, an <a href="https://docs.r-wasm.org/webr/latest/api/js/classes/RWorker.RDouble.html" target="_blank" rel="noopener"><code>RDouble</code></a>
 object is returned at runtime, but <code>webR.evalR()</code> is typed to return a generic <code>RObject</code>. Notice that the <a href="https://docs.r-wasm.org/webr/latest/api/js/classes/RWorker.RDouble.html#tonumber" target="_blank" rel="noopener"><code>.toNumber()</code></a>
 method exists on <code>RDouble</code>, but not on the <code>RObject</code> superclass. So while this example runs with no problem once compiled to JavaScript, it gives an error under TypeScript!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">obj</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="s1">&#39;1.23456&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">toJs</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">num</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">toNumber</span><span class="p">();</span> <span class="c1">// An error under TypeScript!
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>One solution is to use the <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions" target="_blank" rel="noopener"><code>as</code></a>
 keyword to assert a specific type of <code>RObject</code> subclass. Alternatively, webR also provides <a href="https://docs.r-wasm.org/webr/latest/evaluating.html#returning-javascript-values-when-evaluating-r-code" target="_blank" rel="noopener">variants of the <code>evalR()</code> function</a>
 that return and convert results to a specific type of JavaScript object.</p>
<p>In many cases these methods will work well, <em>but they require you to know for sure what type of R object has been returned</em>. Additional support has been added in webR 0.2.0 to better handle typing when it is not entirely clear what type of <code>RObject</code> you have.</p>
<h4 id="type-predicate-functions">Type predicate functions
</h4>
<p>TypeScript supports a kind of return type known as a <a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates" target="_blank" rel="noopener">type predicate</a>
. These return types can be used to create user-defined type guards, functions that take an object argument and return a boolean indicating if the object is of a compatible type. With this, TypeScript is able to automatically <a href="https://www.typescriptlang.org/docs/handbook/2/narrowing.html" target="_blank" rel="noopener">narrow</a>
 types based on the return value from the type predicate function.</p>
<p>WebR 0.2.0 ships with a selection of <a href="https://docs.r-wasm.org/webr/latest/objects.html#type-predicate-functions" target="_blank" rel="noopener">type predicate functions for each fundamental R data type</a>
 supported by webR. In the following example, the TypeScript error described above is dealt with by using the function <a href="https://docs.r-wasm.org/webr/latest/api/js/modules/RMain.html#isrdouble" target="_blank" rel="noopener"><code>isRDouble()</code></a>
. Inside the branch, TypeScript narrows the object type to an <code>RDouble</code>, resolving the issue.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">isRDouble</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;webr&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">obj</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="s1">&#39;1.23456&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isRDouble</span><span class="p">(</span><span class="nx">obj</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// In this branch, TypeScript narrows the type of `obj` to an `RDouble`
</span></span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">num</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">obj</span><span class="p">.</span><span class="nx">toNumber</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">    <span class="c1">// Do something with `num` ...
</span></span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">webR</span><span class="p">.</span><span class="nx">destroy</span><span class="p">(</span><span class="nx">obj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="handling-errors-with-webrerror">Handling errors with <code>WebRError</code>
</h3>
<p>When executing R code with webR&rsquo;s <code>evalR()</code> family of functions, by default any error condition from R is converted into a JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error" target="_blank" rel="noopener"><code>Error</code></a>
 and thrown. This feature can be very useful, because it allows developers to catch issues while executing R code in the native JavaScript environment.</p>
<p>However, consider the following example,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="s1">&#39;some_R_code()&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">doSomethingWith</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Handle some error that occured
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If an error is thrown, how can we tell if the error came from R or from some issue inside the JavaScript function? Nested <code>try</code>/<code>catch</code> could be used, but this becomes unwieldy quickly. Parsing the error message text is another option, though not so elegant.</p>
<p>With webR 0.2.0 any errors that occur in R code executed using <code>evalR()</code>, or any internal webR issues, are thrown as instances of <a href="https://docs.r-wasm.org/webr/latest/api/js/classes/WebR.WebRError.html" target="_blank" rel="noopener"><code>WebRError</code></a>
. With this change, the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof" target="_blank" rel="noopener"><code>instanceof</code></a>
 keyword can be used to differentiate between errors occurring in R, and errors in JavaScript code.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">WebRError</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;webR&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">evalR</span><span class="p">(</span><span class="s1">&#39;some_R_code()&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">doSomethingWith</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">e</span> <span class="k">instanceof</span> <span class="nx">WebRError</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;An error occured executing R code&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;An error occured in JavaScript&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">throw</span> <span class="nx">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="safely-handling-webr-termination">Safely handling webR termination
</h3>
<p>Consider the following <code>async</code> loop, a useful pattern to continuously handle webR output messages,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">run() {</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">read</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="kr">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="s1">&#39;stdout&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="s1">&#39;stderr&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="sb">`Unhandled output type: </span><span class="si">${</span><span class="nx">output</span><span class="p">.</span><span class="kr">type</span><span class="si">}</span><span class="sb">.`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Here <code>await webR.read()</code> waits asynchronously for output messages from webR&rsquo;s communication channel. For example, a running R process might print results between long computational delays. Such occasional printed output might be received as messages with a <code>type</code> property of <code>'stdout'</code>.</p>
<p>After a message is received, it is handled in a <code>switch</code> statement and then the loop continues around to wait for another output message. This works well while webR is running, but what happens when terminated with <a href="https://docs.r-wasm.org/webr/latest/api/js/classes/WebR.WebR.html#close" target="_blank" rel="noopener"><code>webR.close()</code></a>
? The R worker thread is stopped and destroyed, but the loop continues to wait for a message that will never come.</p>
<p>With webR 0.2.0 a new type of message is issued when webR is terminated using <code>webR.close()</code>. After the webR worker thread has been destroyed, a message is emitted on the usual output channel with a <code>type</code> property of <code>'closed'</code>, with no associated <code>data</code> property. The implication is that once this message has been emitted, that particular instance of webR has terminated and the the async loop is no longer needed.</p>
<p>With this change, exiting the loop once webR has terminated could be as simple as adding an extra <code>case</code> statement,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">run() {</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">output</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">webR</span><span class="p">.</span><span class="nx">read</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="kr">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="s1">&#39;stdout&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="s1">&#39;stderr&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">output</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">case</span> <span class="s1">&#39;closed&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="sb">`Unhandled output type: </span><span class="si">${</span><span class="nx">output</span><span class="p">.</span><span class="kr">type</span><span class="si">}</span><span class="sb">.`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="installation-and-next-steps">Installation and next steps
</h2>
<p>Developers can integrate webR in their own JavaScript or TypeScript projects by installing the <a href="https://www.npmjs.com/package/webr" target="_blank" rel="noopener">webR npm package</a>
, or by directly importing webR from CDN. Issues and PRs are accepted and welcome on the main <a href="https://github.com/r-wasm/webr" target="_blank" rel="noopener">r-wasm/webr</a>
 GitHub repository.</p>
<h3 id="npm">npm
</h3>
<p>With this release, the webR npm package name has been updated, simplified from the original <code>@r-wasm/webr</code> package name to simply <code>webr</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm i webr
</span></span></code></pre></td></tr></table>
</div>
</div><p>The original namespaced package <code>@r-wasm/webr</code> will be deprecated, and from v0.2.0 onwards npm will display a message pointing to the new package name.</p>
<h3 id="cdn-url">CDN URL
</h3>
<p>Alternatively, webR can be imported directly as a module from CDN.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">WebR</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;https://webr.r-wasm.org/v0.2.0/webr.mjs&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="binary-release-packages">Binary release packages
</h3>
<p>Finally, binary webR packages can be downloaded from GitHub on the releases page of the <a href="https://github.com/r-wasm/webr" target="_blank" rel="noopener">r-wasm/webr</a>
 repo.</p>
<h3 id="documentation">Documentation
</h3>
<p>The next step of integrating webR into your own software should be to visit the documentation pages, provided at <a href="https://docs.r-wasm.org/webr/v0.2.0/" target="_blank" rel="noopener">https://docs.r-wasm.org/webr/v0.2.0/</a>
. My previous <a href="https://www.tidyverse.org/blog/2023/03/webr-0-1-0/" target="_blank" rel="noopener">webR release blog post</a>
 also briefly explains how to get started, though the docs go into much more detail.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thank you to all of webR&rsquo;s early adopters, experimenting with the system and providing feedback in the form of GitHub Issues and PRs.</p>
<p><a href="https://github.com/Anurodhyadav" target="_blank" rel="noopener">@Anurodhyadav</a>
, <a href="https://github.com/arkraieski" target="_blank" rel="noopener">@arkraieski</a>
, <a href="https://github.com/averissimo" target="_blank" rel="noopener">@averissimo</a>
, <a href="https://github.com/awconway" target="_blank" rel="noopener">@awconway</a>
, <a href="https://github.com/bahadzie" target="_blank" rel="noopener">@bahadzie</a>
, <a href="https://github.com/ceciliacsilva" target="_blank" rel="noopener">@ceciliacsilva</a>
, <a href="https://github.com/DanielEWeeks" target="_blank" rel="noopener">@DanielEWeeks</a>
, <a href="https://github.com/eteitelbaum" target="_blank" rel="noopener">@eteitelbaum</a>
, <a href="https://github.com/fortunewalla" target="_blank" rel="noopener">@fortunewalla</a>
, <a href="https://github.com/gedw99" target="_blank" rel="noopener">@gedw99</a>
, <a href="https://github.com/gwd-at" target="_blank" rel="noopener">@gwd-at</a>
, <a href="https://github.com/hatemhosny" target="_blank" rel="noopener">@hatemhosny</a>
, <a href="https://github.com/hrbrmstr" target="_blank" rel="noopener">@hrbrmstr</a>
, <a href="https://github.com/ivelasq" target="_blank" rel="noopener">@ivelasq</a>
, <a href="https://github.com/JeremyPasco" target="_blank" rel="noopener">@JeremyPasco</a>
, <a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>
, <a href="https://github.com/jooyoungseo" target="_blank" rel="noopener">@jooyoungseo</a>
, <a href="https://github.com/jpjais" target="_blank" rel="noopener">@jpjais</a>
, <a href="https://github.com/kforner" target="_blank" rel="noopener">@kforner</a>
, <a href="https://github.com/lauritowal" target="_blank" rel="noopener">@lauritowal</a>
, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>
, <a href="https://github.com/matthiasbirkich" target="_blank" rel="noopener">@matthiasbirkich</a>
, <a href="https://github.com/neocarto" target="_blank" rel="noopener">@neocarto</a>
, <a href="https://github.com/noamross" target="_blank" rel="noopener">@noamross</a>
, <a href="https://github.com/Polkas" target="_blank" rel="noopener">@Polkas</a>
, <a href="https://github.com/qiushiyan" target="_blank" rel="noopener">@qiushiyan</a>
, <a href="https://github.com/ries9112" target="_blank" rel="noopener">@ries9112</a>
, <a href="https://github.com/SugarRayLua" target="_blank" rel="noopener">@SugarRayLua</a>
, <a href="https://github.com/timelyportfolio" target="_blank" rel="noopener">@timelyportfolio</a>
, <a href="https://github.com/WebReflection" target="_blank" rel="noopener">@WebReflection</a>
, and <a href="https://github.com/WillemSleegers" target="_blank" rel="noopener">@WillemSleegers</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>In addition, <a href="https://blog.djnavarro.net/posts/2023-04-09_webr/" target="_blank" rel="noopener">Danielle Navarro&rsquo;s webR blog post</a>
 is very good and Bob Rudis&rsquo;s <a href="https://rud.is/webr-experiments/" target="_blank" rel="noopener">webR experiments</a>
 are well worth exploring, along with his recent <a href="https://youtu.be/inpwcTUmBDY" target="_blank" rel="noopener">NY R conference talk</a>
.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Also other JavaScript/Wasm environments, such as Node.js. For example, <a href="https://ropensci.org/r-universe/" target="_blank" rel="noopener">ROpenSci&rsquo;s r-universe</a>
 package platform provides download links for datasets contained in R packages, in a variety of formats, <a href="https://fosstodon.org/@jeroenooms/110299179903212170" target="_blank" rel="noopener">powered by running webR server-side in Node.js</a>
.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>REPL stands for &ldquo;Read, Eval, Print, Loop&rdquo;, and is another name for the R console that you&rsquo;re probably familiar with. The application is named the &ldquo;webR REPL app&rdquo; because the original version simply provided the user with a fullscreen R console in their web browser.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>This also includes the world of CSS web fonts, but it is a little tricky. <a href="https://stackoverflow.com/a/53808942" target="_blank" rel="noopener">Extra work</a>
 must be done so that the font is available to the Web Worker. Probably this can be handled better in a future release of <a href="https://docs.r-wasm.org/webr/latest/api/r.html#graphics-device-for-drawing-to-a-html-canvas-element" target="_blank" rel="noopener"><code>webr::canvas()</code></a>
.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://www.dyslexiefont.com" target="_blank" rel="noopener">Dyslexie</a>
, <a href="https://opendyslexic.org" target="_blank" rel="noopener">Open Dyslexic</a>
. Results of research in this area is mixed, but even if these fonts don&rsquo;t improve the speed of text comprehension, some users may simply prefer or feel more comfortable with them.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://shiny.posit.co/py/docs/shinylive.html" target="_blank" rel="noopener">Shinylive for Python</a>
 also uses a JavaScript Service Worker scheme to serve fully client-side apps.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/tidyverse/2023/webr-0-2-0/thumbnail-wd.jpg" length="132537" type="image/jpeg" />
    </item>
    <item>
      <title>Understanding LoRA with a minimal example</title>
      <link>https://posit-open-source.netlify.app/blog/ai/safetensors/</link>
      <pubDate>Thu, 22 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/ai/safetensors/</guid>
      <dc:creator>Daniel Falbel</dc:creator><description><![CDATA[<p>LoRA (Low-Rank Adaptation) is a new technique for fine tuning large scale pre-trained
models. Such models are usually trained on general domain data, so as to have
the maximum amount of data. In order to obtain better results in tasks like chatting
or question answering, these models can be further &lsquo;fine-tuned&rsquo; or adapted on domain
specific data.</p>
<p>It&rsquo;s possible to fine-tune a model just by initializing the model with the pre-trained
weights and further training on the domain specific data. With the increasing size of
pre-trained models, a full forward and backward cycle requires a large amount of computing
resources. Fine tuning by simply continuing training also requires a full copy of all
parameters for each task/domain that the model is adapted to.</p>
<p><a href="https://arxiv.org/abs/2106.09685" target="_blank" rel="noopener">LoRA: Low-Rank Adaptation of Large Language Models</a>

proposes a solution for both problems by using a low rank matrix decomposition.
It can reduce the number of trainable weights by 10,000 times and GPU memory requirements
by 3 times.</p>
<h2 id="method">Method
</h2>
<p>The problem of fine-tuning a neural network can be expressed by finding a $\Delta \Theta$
that minimizes $L(X, y; \Theta_0 + \Delta\Theta)$ where $L$ is a loss function, $X$ and $y$
are the data and $\Theta_0$ the weights from a pre-trained model.</p>
<p>We learn the parameters $\Delta \Theta$ with dimension $|\Delta \Theta|$
equals to $|\Theta_0|$. When $|\Theta_0|$ is very large, such as in large scale
pre-trained models, finding $\Delta \Theta$ becomes computationally challenging.
Also, for each task you need to learn a new $\Delta \Theta$ parameter set, making
it even more challenging to deploy fine-tuned models if you have more than a
few specific tasks.</p>
<p>LoRA proposes using an approximation $\Delta \Phi \approx \Delta \Theta$ with $|\Delta \Phi| << |\Delta \Theta|$.
The observation is that neural nets have many dense layers performing matrix multiplication,
and while they typically have full-rank during pre-training, when adapting to a specific task
the weight updates will have a low &ldquo;intrinsic dimension&rdquo;.</p>
<p>A simple matrix decomposition is applied for each weight matrix update $\Delta \theta \in \Delta \Theta$.
Considering $\Delta \theta_i \in \mathbb{R}^{d \times k}$ the update for the $i$th weight
in the network, LoRA approximates it with:</p>
$$\Delta \theta_i  \approx \Delta \phi_i = BA$$<p>
where $B \in \mathbb{R}^{d \times r}$, $A \in \mathbb{R}^{r \times d}$ and the rank $r << min(d, k)$.
Thus instead of learning $d \times k$ parameters we now need to learn $(d + k) \times r$ which is easily
a lot smaller given the multiplicative aspect. In practice, $\Delta \theta_i$ is scaled
by $\frac{\alpha}{r}$ before being added to $\theta_i$, which can be interpreted as a
&rsquo;learning rate&rsquo; for the LoRA update.</p>
<p>LoRA does not increase inference latency, as once fine tuning is done, you can simply
update the weights in $\Theta$ by adding their respective $\Delta \theta \approx \Delta \phi$.
It also makes it simpler to deploy multiple task specific models on top of one large model,
as $|\Delta \Phi|$ is much smaller than $|\Delta \Theta|$.</p>
<h2 id="implementing-in-torch">Implementing in torch
</h2>
<p>Now that we have an idea of how LoRA works, let&rsquo;s implement it using torch for a
minimal problem. Our plan is the following:</p>
<ol>
<li>Simulate training data using a simple $y = X \theta$ model. $\theta \in \mathbb{R}^{1001, 1000}$.</li>
<li>Train a full rank linear model to estimate $\theta$ - this will be our &lsquo;pre-trained&rsquo; model.</li>
<li>Simulate a different distribution by applying a transformation in $\theta$.</li>
<li>Train a low rank model using the pre=trained weights.</li>
</ol>
<p>Let&rsquo;s start by simulating the training data:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">torch</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">n</span> <span class="o">&lt;-</span> <span class="m">10000</span>
</span></span><span class="line"><span class="cl"><span class="n">d_in</span> <span class="o">&lt;-</span> <span class="m">1001</span>
</span></span><span class="line"><span class="cl"><span class="n">d_out</span> <span class="o">&lt;-</span> <span class="m">1000</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">thetas</span> <span class="o">&lt;-</span> <span class="nf">torch_randn</span><span class="p">(</span><span class="n">d_in</span><span class="p">,</span> <span class="n">d_out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">X</span> <span class="o">&lt;-</span> <span class="nf">torch_randn</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">d_in</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">y</span> <span class="o">&lt;-</span> <span class="nf">torch_matmul</span><span class="p">(</span><span class="n">X</span><span class="p">,</span> <span class="n">thetas</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We now define our base model:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">model</span> <span class="o">&lt;-</span> <span class="nf">nn_linear</span><span class="p">(</span><span class="n">d_in</span><span class="p">,</span> <span class="n">d_out</span><span class="p">,</span> <span class="n">bias</span> <span class="o">=</span> <span class="kc">FALSE</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We also define a function for training a model, which we are also reusing later.
The function does the standard traning loop in torch using the Adam optimizer.
The model weights are updated in-place.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">train</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">batch_size</span> <span class="o">=</span> <span class="m">128</span><span class="p">,</span> <span class="n">epochs</span> <span class="o">=</span> <span class="m">100</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">opt</span> <span class="o">&lt;-</span> <span class="nf">optim_adam</span><span class="p">(</span><span class="n">model</span><span class="o">$</span><span class="n">parameters</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">for</span> <span class="p">(</span><span class="n">epoch</span> <span class="kr">in</span> <span class="m">1</span><span class="o">:</span><span class="n">epochs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">for</span><span class="p">(</span><span class="n">i</span> <span class="kr">in</span> <span class="nf">seq_len</span><span class="p">(</span><span class="n">n</span><span class="o">/</span><span class="n">batch_size</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">idx</span> <span class="o">&lt;-</span> <span class="nf">sample.int</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="n">batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">loss</span> <span class="o">&lt;-</span> <span class="nf">nnf_mse_loss</span><span class="p">(</span><span class="nf">model</span><span class="p">(</span><span class="n">X[idx</span><span class="p">,</span><span class="n">]</span><span class="p">),</span> <span class="n">y[idx]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      
</span></span><span class="line"><span class="cl">      <span class="nf">with_no_grad</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="n">opt</span><span class="o">$</span><span class="nf">zero_grad</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">loss</span><span class="o">$</span><span class="nf">backward</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">opt</span><span class="o">$</span><span class="nf">step</span><span class="p">()</span>  
</span></span><span class="line"><span class="cl">      <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="kr">if</span> <span class="p">(</span><span class="n">epoch</span> <span class="o">%%</span> <span class="m">10</span> <span class="o">==</span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nf">with_no_grad</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="n">loss</span> <span class="o">&lt;-</span> <span class="nf">nnf_mse_loss</span><span class="p">(</span><span class="nf">model</span><span class="p">(</span><span class="n">X</span><span class="p">),</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="nf">cat</span><span class="p">(</span><span class="s">&#34;[&#34;</span><span class="p">,</span> <span class="n">epoch</span><span class="p">,</span> <span class="s">&#34;] Loss:&#34;</span><span class="p">,</span> <span class="n">loss</span><span class="o">$</span><span class="nf">item</span><span class="p">(),</span> <span class="s">&#34;\n&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The model is then trained:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">train</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 10 ] Loss: 577.075 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 20 ] Loss: 312.2 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 30 ] Loss: 155.055 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 40 ] Loss: 68.49202 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 50 ] Loss: 25.68243 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 60 ] Loss: 7.620944 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 70 ] Loss: 1.607114 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 80 ] Loss: 0.2077137 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 90 ] Loss: 0.01392935 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 100 ] Loss: 0.0004785107</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>OK, so now we have our pre-trained base model. Let&rsquo;s suppose that we have data from
a slighly different distribution that we simulate using:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">thetas2</span> <span class="o">&lt;-</span> <span class="n">thetas</span> <span class="o">+</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">X2</span> <span class="o">&lt;-</span> <span class="nf">torch_randn</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">d_in</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">y2</span> <span class="o">&lt;-</span> <span class="nf">torch_matmul</span><span class="p">(</span><span class="n">X2</span><span class="p">,</span> <span class="n">thetas2</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If we apply out base model to this distribution, we don&rsquo;t get a good performance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">nnf_mse_loss</span><span class="p">(</span><span class="nf">model</span><span class="p">(</span><span class="n">X2</span><span class="p">),</span> <span class="n">y2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; torch_tensor</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 992.673</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ CPUFloatType{} ][ grad_fn = &lt;MseLossBackward0&gt; ]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We now fine-tune our initial model. The distribution of the new data is just slighly
different from the initial one. It&rsquo;s just a rotation of the data points, by adding 1
to all thetas. This means that the weight updates are not expected to be complex, and
we shouldn&rsquo;t need a full-rank update in order to get good results.</p>
<p>Let&rsquo;s define a new torch module that implements the LoRA logic:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">lora_nn_linear</span> <span class="o">&lt;-</span> <span class="nf">nn_module</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="n">initialize</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">linear</span><span class="p">,</span> <span class="n">r</span> <span class="o">=</span> <span class="m">16</span><span class="p">,</span> <span class="n">alpha</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">$</span><span class="n">linear</span> <span class="o">&lt;-</span> <span class="n">linear</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># parameters from the original linear module are &#39;freezed&#39;, so they are not</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># tracked by autograd. They are considered just constants.</span>
</span></span><span class="line"><span class="cl">    <span class="n">purrr</span><span class="o">::</span><span class="nf">walk</span><span class="p">(</span><span class="n">self</span><span class="o">$</span><span class="n">linear</span><span class="o">$</span><span class="n">parameters</span><span class="p">,</span> <span class="nf">\</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="n">x</span><span class="o">$</span><span class="nf">requires_grad_</span><span class="p">(</span><span class="kc">FALSE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># the low rank parameters that will be trained</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">$</span><span class="n">A</span> <span class="o">&lt;-</span> <span class="nf">nn_parameter</span><span class="p">(</span><span class="nf">torch_randn</span><span class="p">(</span><span class="n">linear</span><span class="o">$</span><span class="n">in_features</span><span class="p">,</span> <span class="n">r</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">$</span><span class="n">B</span> <span class="o">&lt;-</span> <span class="nf">nn_parameter</span><span class="p">(</span><span class="nf">torch_zeros</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">linear</span><span class="o">$</span><span class="n">out_feature</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1"># the scaling constant</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">$</span><span class="n">scaling</span> <span class="o">&lt;-</span> <span class="n">alpha</span> <span class="o">/</span> <span class="n">r</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="n">forward</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># the modified forward, that just adds the result from the base model</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># and ABx.</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">$</span><span class="nf">linear</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">+</span> <span class="nf">torch_matmul</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nf">torch_matmul</span><span class="p">(</span><span class="n">self</span><span class="o">$</span><span class="n">A</span><span class="p">,</span> <span class="n">self</span><span class="o">$</span><span class="n">B</span><span class="p">)</span><span class="o">*</span><span class="n">self</span><span class="o">$</span><span class="n">scaling</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We now initialize the LoRA model. We will use $r = 1$, meaning that A and B will be just
vectors. The base model has 1001x1000 trainable parameters. The LoRA model that we are
are going to fine tune has just (1001 + 1000) which makes it 1/500 of the base model
parameters.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">lora</span> <span class="o">&lt;-</span> <span class="nf">lora_nn_linear</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">r</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now let&rsquo;s train the lora model on the new distribution:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">train</span><span class="p">(</span><span class="n">lora</span><span class="p">,</span> <span class="n">X2</span><span class="p">,</span> <span class="n">Y2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 10 ] Loss: 798.6073 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 20 ] Loss: 485.8804 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 30 ] Loss: 257.3518 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 40 ] Loss: 118.4895 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 50 ] Loss: 46.34769 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 60 ] Loss: 14.46207 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 70 ] Loss: 3.185689 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 80 ] Loss: 0.4264134 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 90 ] Loss: 0.02732975 </span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ 100 ] Loss: 0.001300132 </span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If we look at $\Delta \theta$ we will see a matrix full of 1s, the exact transformation
that we applied to the weights:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">delta_theta</span> <span class="o">&lt;-</span> <span class="nf">torch_matmul</span><span class="p">(</span><span class="n">lora</span><span class="o">$</span><span class="n">A</span><span class="p">,</span> <span class="n">lora</span><span class="o">$</span><span class="n">B</span><span class="p">)</span><span class="o">*</span><span class="n">lora</span><span class="o">$</span><span class="n">scaling</span>
</span></span><span class="line"><span class="cl"><span class="n">delta_theta[1</span><span class="o">:</span><span class="m">5</span><span class="p">,</span> <span class="m">1</span><span class="o">:</span><span class="m">5</span><span class="n">]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; torch_tensor</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;  1.0002  1.0001  1.0001  1.0001  1.0001</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;  1.0011  1.0010  1.0011  1.0011  1.0011</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;  0.9999  0.9999  0.9999  0.9999  0.9999</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;  1.0015  1.0014  1.0014  1.0014  1.0014</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;  1.0008  1.0008  1.0008  1.0008  1.0008</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ CPUFloatType{5,5} ][ grad_fn = &lt;SliceBackward0&gt; ]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To avoid the additional inference latency of the separate computation of the deltas,
we could modify the original model by adding the estimated deltas to its parameters.
We use the <code>add_</code> method to modify the weight in-place.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">with_no_grad</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="n">model</span><span class="o">$</span><span class="n">weight</span><span class="o">$</span><span class="nf">add_</span><span class="p">(</span><span class="n">delta_theta</span><span class="o">$</span><span class="nf">t</span><span class="p">())</span>  
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, applying the base model to data from the new distribution yields good performance,
so we can say the model is adapted for the new task.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">nnf_mse_loss</span><span class="p">(</span><span class="nf">model</span><span class="p">(</span><span class="n">X2</span><span class="p">),</span> <span class="n">y2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; torch_tensor</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 0.00130013</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [ CPUFloatType{} ]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="concluding">Concluding
</h2>
<p>Now that we learned how LoRA works for this simple example we can think how it could
work on large pre-trained models.</p>
<p>Turns out that Transformers models are mostly clever organization of these matrix
multiplications, and applying LoRA only to these layers is enough for reducing the
fine tuning cost by a large amount while still getting good performance. You can see
the experiments in the LoRA paper.</p>
<p>Of course, the idea of LoRA is simple enough that it can be applied not only to
linear layers. You can apply it to convolutions, embedding layers and actually any other layer.</p>
<p>Image by Hu et al on the <a href="https://arxiv.org/abs/2106.09685" target="_blank" rel="noopener">LoRA paper</a>
</p>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/ai/safetensors/thumbnail.png" length="56356" type="image/png" />
    </item>
    <item>
      <title>What are Large Language Models? What are they not?</title>
      <link>https://posit-open-source.netlify.app/blog/ai/keydanallm/</link>
      <pubDate>Tue, 20 Jun 2023 00:00:00 +0000</pubDate>
      <guid>https://posit-open-source.netlify.app/blog/ai/keydanallm/</guid>
      <dc:creator>Sigrid Keydana</dc:creator><description><![CDATA[<blockquote>
<p>&ldquo;At this writing, the only serious ELIZA scripts which exist are some which cause ELIZA to respond roughly as would certain psychotherapists (Rogerians). ELIZA performs best when its human correspondent is initially instructed to&quot;talk&rdquo; to it, via the typewriter of course, just as one would to a psychiatrist. This mode of conversation was chosen because the psychiatric interview is one of the few examples of categorized dyadic natural language communication in which one of the participating pair is free to assume the pose of knowing almost nothing of the real world. If, for example, one were to tell a psychiatrist &ldquo;I went for a long boat ride&rdquo; and he responded &ldquo;Tell me about boats&rdquo;, one would not assume that he knew nothing about boats, but that he had some purpose in so directing the subsequent conversation. It is important to note that this assumption is one made by the speaker. Whether it is realistic or not is an altogether separate question. In any case, it has a crucial psychological utility in that it serves the speaker to maintain his sense of being heard and understood. The speaker furher defends his impression (which even in real life may be illusory) by attributing to his conversational partner all sorts of background knowledge, insights and reasoning ability. But again, these are the speaker&rsquo;s contribution to the conversation.&quot;</p>
<p>Joseph Weizenbaum, creator of ELIZA (Weizenbaum 1966).</p>
</blockquote>
<p>GPT, the ancestor all numbered <a href="https://en.wikipedia.org/wiki/Generative_pre-trained_transformer" target="_blank" rel="noopener">GPTs</a>
, was released in June, 2018 &ndash; five years ago, as I write this. Five years: that&rsquo;s a long time. It certainly is as measured on the time scale of deep learning, the thing that is, usually, behind when people talk of &ldquo;AI&rdquo;. One year later, GPT was followed by GPT-2; another year later, by GPT-3. At this point, public attention was still modest &ndash; as expected, really, for these kinds of technologies that require lots of specialist knowledge. (For GPT-2, what may have increased attention beyond the normal, a bit, was OpenAI &rsquo;s refusal to publish the complete training code and full model weights, supposedly due to the threat posed by the model&rsquo;s capabilities &ndash; alternatively, as argued by others, as a marketing strategy, or yet alternatively, as a way to preserve one&rsquo;s own competitive advantage just a tiny little bit longer.</p>
<p>As of 2023, with GPT-3.5 and GPT-4 having followed, everything looks different. (Almost) everyone seems to know GPT, at least when that acronym appears prefixed by a certain syllable. Depending on who you talk to, people don&rsquo;t seem to stop talking about that fantastic [insert thing here] ChatGPT generated for them, about its enormous usefulness with respect to [insert goal here]&hellip; or about the flagrant mistakes it made, and the danger that legal regulation and political enforcement will never be able to catch up.</p>
<p>What made the difference? Obviously, it&rsquo;s <a href="https://en.wikipedia.org/wiki/ChatGPT" target="_blank" rel="noopener">ChatGPT</a>
, or put differently, the fact that now, there is a means for people to make active use of such a tool, employing it for whatever their personal needs or interests are<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. In fact, I&rsquo;d argue it&rsquo;s more than that: ChatGPT is not some impersonal tool &ndash; it <em>talks</em> to you, picking up your clarifications, changes of topic, mood&hellip; It is <em>someone</em> rather than <em>something</em>, or at least that&rsquo;s how it seems. I&rsquo;ll come back to that point in <a href="#its-us-really-anthropomorphism-unleashed">It&rsquo;s us, really: Anthropomorphism unleashed</a>
. Before, let&rsquo;s take a look at the underlying technology.</p>
<h2 id="large-language-models-what-they-are">Large Language Models: What they are
</h2>
<p>How is it even possible to build a machine that talks to you? One way is to have that machine <em>listen</em> a lot. And listen is what these machines do; they do it a lot. But listening alone would never be enough to attain results as impressive as those we see. Instead, LLMs practice some form of &ldquo;maximally active listening&rdquo;: Continuously, they try to predict the speaker&rsquo;s next utterance. By &ldquo;continuously&rdquo;, I mean word-by-word: At each training step, the model is asked to produce the subsequent word in a text.</p>
<p>Maybe in my last sentence, you noted the term &ldquo;train&rdquo;. As per common sense, &ldquo;training&rdquo; implies some form of supervision. It also implies some form of method. Since learning material is scraped from the internet, the true continuation is always known. The precondition for supervision is thus always fulfilled: A supervisor can just compare model prediction with what really follows in the text. Remains the question of method. That&rsquo;s where we need to talk about deep learning, and we&rsquo;ll do that in <a href="#model-training">Model training</a>
.</p>
<h3 id="overall-architecture">Overall architecture
</h3>
<p>Today&rsquo;s LLMs are, in some way or the other, based on an architecture known as the <em>Transformer</em>. This architecture was originally introduced in a paper catchily titled &ldquo;Attention is all you need&rdquo; (Vaswani et al. 2017). Of course, this was not the first attempt at automating natural-language generation &ndash; not even in deep learning, the sub-type of machine learning whose defining characteristic are many-layered (&ldquo;deep&rdquo;) artificial neural networks. But there, in deep learning, it constituted some kind of paradigm change. Before, models designed to solve sequence-prediction tasks (time-series forecasting, text generation&hellip;) tended to be based on some form of recurrent architecture, introduced in the 1990&rsquo;s (eternities ago, on the time scale of deep-learning) by (Hochreiter and Schmidhuber 1997). Basically, the concept of recurrence, with its associated threading of a latent state, was replaced by &ldquo;attention&rdquo;. That&rsquo;s what the paper&rsquo;s title was meant to communicate: The authors did not <em>introduce</em> &ldquo;attention&rdquo;<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>; instead, they fundamentally expanded its usage so as to render recurrence superfluous.</p>
<p>How did that ancestral Transformer look? &ndash; One prototypical task in natural language processing is machine translation. In translation, be it done by a machine or by a human, there is an input (in one language) and an output (in another). That input, call it a <em>code</em>. Whoever wants to establish its counterpart in the target language first needs to <em>decode</em> it. Indeed, one of two top-level building blocks of the archetypal Transformer was a decoder, or rather, a stack of decoders applied in succession. At its end, out popped a phrase in the target language<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. What, then, was the other high-level block? It was an <em>encoder</em>, something that takes text (or tokens, rather, i.e., something that has undergone tokenization) and converts it into a form the decoder can make sense of. (Obviously, there is no analogue to this in human translation.)</p>
<p>From this two-stack architecture, subsequent developments tended to keep just one. The GPT family, together with many others, just kept the decoder stack. Now, doesn&rsquo;t the decoder need <em>some</em> kind of input &ndash; if not to translate to a different language, then to reply to, as in the chatbot scenario? Turns out that no, it doesn&rsquo;t &ndash; and that&rsquo;s why you can also have the bot initiate the conversation. Unbeknownst to you, there will, in fact, be an input to the model &ndash; some kind of token signifying &ldquo;end of input&rdquo;. In that case, the model will draw on its training experience to generate a word likely to start out a phrase. That one word will then become the new input to continue from, and so forth. Summing up so far, then, GPT-like LLMs are <em>Transformer Decoders</em>.</p>
<p>The question is, how does such a stack of decoders succeed in fulfilling the task?</p>
<h3 id="gpt-type-models-up-close">GPT-type models up close
</h3>
<p>In opening the black box, we focus on its two interfaces &ndash; input and output &ndash; as well as on the internals, its core.</p>
<h4 id="input">Input
</h4>
<p>For simplicity, let me speak of words, not tokens. Now imagine a machine that is to work with &ndash; more even: &ldquo;understand&rdquo;<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> &ndash; words. For a computer to process non-numeric data, a conversion to numbers necessarily has to happen. The straightforward way to effectuate this is to decide on a fixed lexicon, and assign each word a number. And this works: The way deep neural networks are trained, they don&rsquo;t need semantic relationships to exist between entities in the training data to memorize formal structure. Does this mean they will appear perfect while training, but fail in real-world prediction? &ndash; If the training data are representative of how we converse, all will be fine. In a world of perfect surveillance, machines could exist that have internalized our every spoken word. Before that happens, though, the training data will be imperfect.</p>
<p>A much more promising approach than to simply index words, then, is to represent them in a richer, higher-dimensional space, an <em>embedding</em> space. This idea, popular not just in deep learning but in natural language processing overall, really goes far beyond anything domain-specific &ndash; linguistic entities, say<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>. You may be able to fruitfully employ it in virtually any domain &ndash; provided you can devise a method to sensibly map the given data into that space. In deep learning, these embeddings are obtained in a clever way: as a by-product of sorts of the overall training workflow. Technically, this is achieved by means of a dedicated neural-network layer<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> tasked with evolving these mappings. Note how, smart though this strategy may be, it implies that the overall setting &ndash; everything from training data via model architecture to optimization algorithms employed &ndash; necessarily affects the resulting embeddings. And since these may be extracted and made use of in down-stream tasks, this matters<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.</p>
<p>As to the GPT family, such an embedding layer constitutes part of its input interface &ndash; one &ldquo;half&rdquo;, so to say. Technically, the second makes use of the same type of layer, but with a different purpose. To contrast the two, let me spell out clearly what, in the part we&rsquo;ve talked about already, is getting mapped to what. The mapping is between a word index &ndash; a sequence <code>1, 2, …, &lt;vocabulary size&gt;</code> &ndash; on the one hand and a set of continuous-valued vectors of some length &ndash; 100, say &ndash; on the other. (One of them could like this: $\begin{bmatrix} 1.002 & 0.71 & 0.0004 &...\\ \end{bmatrix}$) Thus, we obtain an embedding for every word. But language is more than an unordered assembly of words. Rearranging words, if syntactically allowed, may result in drastically changed semantics. In the pre-transformer paradigma, threading a sequentially-updated hidden state took care of this. Put differently, in that type of model, information about input order never got lost throughout the layers. Transformer-type architectures, however, need to find a different way. Here, a variety of rivaling methods exists. Some assume an underlying periodicity in semanto-syntactic structure. Others &ndash; and the GPT family, as yet and <em>insofar</em> <em>we know</em>, has been part of them<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> &ndash; approach the challenge in exactly the same way as for the lexical units: They make learning these so-called <em>position embeddings</em> a by-product of model training. Implementation-wise, the only difference is that now the input to the mapping looks like this: <code>1, 2, …, &lt;maximum position&gt;</code> where &ldquo;maximum position&rdquo; reflects choice of maximal sequence length supported.</p>
<p>Summing up, verbal input is thus encoded &ndash; <em>embedded</em>, enriched &ndash; twofold as it enters the machine. The two types of embedding are combined and passed on to the model core, the already-mentioned decoder stack.</p>
<h4 id="core-processing">Core Processing
</h4>
<p>The decoder stack is made up of some number of identical blocks (12, in the case of GPT-2). (By &ldquo;identical&rdquo; I mean that the architecture is the same; the <em>weights</em> &ndash; the place where a neural-network layer stores what it &ldquo;knows&rdquo; &ndash; are not. More on these &ldquo;weights&rdquo; soon.)</p>
<p>Inside each block, some sub-layers are pretty much &ldquo;business as usual&rdquo;. One is not: the attention module, the &ldquo;magic&rdquo; ingredient that enabled Transformer-based architectures to forego keeping a latent state. To explain how this works, let&rsquo;s take translation as an example.</p>
<p>In the classical encoder-decoder setup, the one most intuitive for machine translation, imagine the very first decoder in the stack of decoders. It receives as input a length-seven cypher, the encoded version of an original length-seven phrase. Since, due to how the encoder blocks are built, input order is conserved, we have a faithful representation of source-language word order. In the target language, however, word order can be very different. A decoder module, in producing the translation, had rather not do this by translating each word as it appears. Instead, it would be desirable for it to know which among the already-seen tokens is most relevant right now, to generate the very next output token. Put differently, it had better know where to direct its <em>attention</em>.</p>
<p>Thus, figure out how to distribute focus is what attention modules do. How do they do it? They compute, for each available input-language token, how good a match, a fit, it is for their own current input. Remember that every token, at every processing stage, is encoded as a vector of continuous values. How good a match any of, say, three source-language vectors is is then computed by projecting one&rsquo;s current input vector onto each of the three. The closer the vectors, the longer the projected vector. <sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> Based on the projection onto each source-input token, that token is weighted, and the attention module passes on the aggregated assessments to the ensuing neural-network module.</p>
<p>To explain what attention modules are for, I&rsquo;ve made use of the machine-translation scenario, a scenario that should lend a certain intuitiveness to the operation. But for GPT-family models, we need to abstract this a bit. First, there is no encoder stack, so &ldquo;attention&rdquo; is computed among decoder-resident tokens only. And second &ndash; remember I said a stack was built up of identical modules? &ndash; this happens in every decoder block. That is, when intermediate results are bubbled up the stack, at each stage the input is weighted as appropriate <em>at that stage</em>. While this is harder to intuit than what happened in the translation scenario, I&rsquo;d argue that in the abstract, it makes a lot of sense. For an analogy, consider some form of hierarchical categorization of entities. As higher-level categories are built from lower-level ones, at each stage the process needs to look at its input afresh, and decide on a sensible way of subsuming similar-in-some-way categories.</p>
<h4 id="output">Output
</h4>
<p>Stack of decoders traversed, the multi-dimensional codes that pop out need to be converted into something that can be compared with the actual phrase continuation we see in the training corpus. Technically, this involves a projection operation as well a strategy for picking the output word &ndash; that word in target-language vocabulary that has the highest probability. How do you decide on a strategy? I&rsquo;ll say more about that in the section <a href="#mechanics-of-text-generation">Mechanics of text generation</a>
, where I assume a chatbot user&rsquo;s perspective.</p>
<h3 id="model-training">Model training
</h3>
<p>Before we get there, just a quick word about model training. LLMs are deep neural networks, and as such, they are trained like any network is. First, assuming you have access to the so-called &ldquo;ground truth&rdquo;, you can always compare model prediction with the true target. You then quantify the difference &ndash; by which algorithm will affect training results. Then, you communicate that difference &ndash; the <em>loss</em> &ndash; to the network. It, in turn, goes through its modules, from back/top to start/bottom, and updates its stored &ldquo;knowledge&rdquo; &ndash; matrices of continuous numbers called <em>weights</em>. Since information is passed from layer to layer, in a direction reverse to that followed in computing predictions, this technique is known as <em>back-propagation</em>.</p>
<p>And all that is not triggered once, but iteratively, for a certain number of so-called &ldquo;epochs&rdquo;, and modulated by a set of so-called &ldquo;hyper-parameters&rdquo;. In practice, a lot of experimentation goes into deciding on the best-working configuration of these settings.</p>
<h3 id="mechanics-of-text-generation">Mechanics of text generation
</h3>
<p>We already know that during model training, predictions are generated word-by-word; at every step, the model&rsquo;s knowledge about what has been said so far is augmented by one token: the word that really was following at that point. If, making use of a trained model, a bot is asked to reply to a question, its response must by necessity be generated in the same way. However, the actual &ldquo;correct word&rdquo; is not known. The only way, then, is to feed back to the model its own most recent prediction. (By necessity, this lends to text generation a very special character, where every decision the bot makes co-determines its future behavior.)</p>
<p>Why, though, talk about decisions? Doesn&rsquo;t the bot just act on behalf of the core model, the LLM &ndash; thus passing on the final output? Not quite. At each prediction step, the model yields a vector, with values as many as there are entries in the vocabulary. As per model design and training rationale, these vectors are &ldquo;scores&rdquo; &ndash; ratings, sort of, how good a fit a word would be in this situation. Like in life, higher is better. But that doesn&rsquo;t mean you&rsquo;d just pick the word with the highest value. In any case, these scores are converted to probabilities, and a suitable probability distribution is used to non-deterministically pick a likely (or likely-ish) word. The probability distribution commonly used is the multinomial distribution, appropriate for discrete choice among more than two alternatives. But what about the conversion to probabilities? Here, there is room for experimentation.</p>
<p>Technically, the algorithm employed is known as the <em>softmax</em> function. It is a simplified version of the <a href="https://en.wikipedia.org/wiki/Boltzmann_distribution" target="_blank" rel="noopener">Boltzmann distribution</a>
, famous in statistical mechanics, used to obtain the probability of a system&rsquo;s state given that state&rsquo;s energy and the temperature of the system. But for temperature<sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup>, both formulae are, in fact, identical. In physical systems, temperature modulates probabilities in the following way: The hotter the system, the closer the states&rsquo; probabilities are to each other; the colder it gets, the more distinct those probabilities. In the extreme, at very low temperatures there will be a few clear &ldquo;winners&rdquo; and a silent majority of &ldquo;losers&rdquo;.</p>
<p>In deep learning, a like effect is easy to achieve (by means of a scaling factor). That&rsquo;s why you may have heard people talk about some weird thing called &ldquo;temperature&rdquo; that resulted in [insert adjective here] answers. If the application you use lets you vary that factor, you&rsquo;ll see that a low temperature will result in deterministic-looking, repetitive, &ldquo;boring&rdquo; continuations, while a high one may make the machine appear as though it were on drugs.</p>
<p>That concludes our high-level overview of LLMs. Having seen the machine dissected in this way may already have left you with some sort of opinion of what these models are &ndash; not. This topic more than deserves a dedicated exposition &ndash; and papers are being written pointing to important aspects all the time &ndash; but in this text, I&rsquo;d like to at least offer some input for thought.</p>
<h2 id="large-language-models-what-they-are-not">Large Language Models: What they are not
</h2>
<p>In part one,describing LLMs technically, I&rsquo;ve sometimes felt tempted to use terms like &ldquo;understanding&rdquo; or &ldquo;knowledge&rdquo; when applied to the machine. I may have ended up using them; in that case, I&rsquo;ve tried to remember to always surround them with quotes. The latter, the adding quotes, stands in contrast to many texts, even ones published in an academic context (Bender and Koller 2020). The question is, though: Why did I even feel compelled to use these terms, given I do <em>not</em> think they apply, in their usual meaning? I can think of a simple &ndash; shockingly simple, maybe &ndash; answer: It&rsquo;s because us, humans, we think, talk, share our thoughts in these terms. When I say <em>understand</em>, I surmise you will <em>know</em> what I <em>mean</em>.</p>
<p>Now, why do I think that these machines do not <em>understand</em> human language, in the sense we usually imply when using that word?</p>
<h3 id="a-few-facts">A few facts
</h3>
<p>I&rsquo;ll start out briefly mentioning empirical results, conclusive thought experiments, and theoretical considerations. All aspects touched upon (and many more) are more than worthy of in-depth discussion, but such discussion is clearly out of scope for this synoptic-in-character text.</p>
<p>First, while it is hard to put a number on the quality of a chatbot&rsquo;s answers, performance on standardized benchmarks is the &ldquo;bread and butter&rdquo; of machine learning &ndash; its reporting being an essential part of the prototypical deep-learning publication. (You could even call it the &ldquo;cookie&rdquo;, the driving incentive, since models usually are explicitly trained and fine-tuned for good results on these benchmarks.) And such benchmarks exist for most of the down-stream tasks the LLMs are used for: machine translation, generating summaries, text classification, and even rather ambitious-sounding setups associated with &ndash; quote/unquote &ndash; reasoning.</p>
<p>How do you assess such a capability? Here is an example from a benchmark named &ldquo;Argument Reasoning Comprehension Task&rdquo; (Habernal et al. 2018).</p>
<pre><code>Claim: Google is not a harmful monopoly
Reason: People can choose not to use Google
Warrant: Other search engines don’t redirect to Google
Alternative: All other search engines redirect to Google
</code></pre>
<p>Here <em>claim</em> and <em>reason</em> together make up the <em>argument</em>. But what, exactly, is it that links them? At first look, this can even be confusing to a human. The missing link is what is called warrant here &ndash; add it in, and it all starts to make sense. The task, then, is to decide which of warrant or alternative supports the conclusion, and which one does not.</p>
<p>If you think about it, this is a surprisingly challenging task. Specifically, it seems to inescapingly require <em>world knowledge</em>. So if language models, as has been claimed, perform nearly as well as humans, it seems they must have such knowledge &ndash; no quotes added. However, in response to such claims, research has been performed to uncover the hidden mechanism that enables such seemingly-superior results. For that benchmark, it has been found (Niven and Kao 2019) that there were spurious statistical cues in the way the dataset was constructed &ndash; those removed, LLM performance was no better than random.</p>
<p>World knowledge, in fact, is one of the main things an LLM lacks. Bender et al. (Bender and Koller 2020) convincingly demonstrate its essentiality by means of two thought experiments. One of them, situated on a lone island, imagines an octopus<sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup> inserting itself into some cable-mediated human communication, learning the chit-chat, and finally &ndash; having gotten bored &ndash; impersonating one of the humans. This works fine, until one day, its communication partner finds themselves in an emergency, and needs to build some rescue tool out of things given in the environment. They urgently ask for advice &ndash; and the octopus has no idea what to respond. It has no ideas what these words actually <em>refer to</em>.</p>
<p>The other argument comes directly from machine learning, and strikingly simple though it may be, it makes its point very well. Imagine an LLM trained as usual, including on lots of text involving plants. It has also been trained on a dataset of unlabeled photos, the actual task being unsubstantial &ndash; say it had to fill out masked areas. Now, we pull out a picture and ask: How many of that blackberry&rsquo;s blossoms have already opened? The model has no chance to answer the question.</p>
<p>Now, please look back at the Joseph Weizenbaum quote I opened this article with. It is still true that language-generating machine have no knowledge of the world we live in.</p>
<p>Before moving on, I&rsquo;d like to just quickly hint at a totally different type of consideration, brought up in a (2003!) paper by Spärck Jones (Spaerck 2004). Though written long before LLMs, and long before deep learning started its winning conquest, on an abstract level it is still very applicable to today&rsquo;s situation. Today, LLMs are employed to &ldquo;learn language&rdquo;, i.e., for language acquisition. That skill is then built upon by specialized models, of task-dependent architecture. Popular real-world<sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> down-stream tasks are translation, document retrieval, or text summarization. When the paper was written, there was no such two-stage pipeline. The author was questioning the fit between how language modeling was conceptualized &ndash; namely, as a form of <em>recovery</em> &ndash; and the character of these down-stream tasks. Was recovery &ndash; inferring a missing, for whatever reasons &ndash; piece of text a good model, of, say, condensing a long, detailed piece of text into a short, concise, factual one? If not, could the reason it still seemed to work just fine be of a very different nature &ndash; a technical, operational, coincidental one?</p>
<blockquote>
<p>[&hellip;] the crucial characterisation of the relationship between the input and the output is in fact offloaded in the LM approach onto the choice of training data. We can use LM for summarising because we know that some set of training data consists of full texts paired with their summaries.<sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup></p>
</blockquote>
<p>It seems to me that today&rsquo;s two-stage process notwithstanding, this is still an aspect worth giving some thought.</p>
<h3 id="its-us-language-learning-shared-goals-and-a-shared-world">It&rsquo;s us: Language learning, shared goals, and a shared world
</h3>
<p>We&rsquo;ve already talked about world knowledge. What else are LLMs missing out on?</p>
<p>In our world, you&rsquo;ll hardly find anything that does not involve other people. This goes a lot deeper than the easily observable facts: our constantly communicating, reading and typing messages, documenting our lives on social networks&hellip; We don&rsquo;t experience, explore, explain a world of our own. Instead, all these activities are inter-subjectively constructed. Feelings are<sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup>. Cognition is; meaning is. And it goes deeper yet. Implicit assumptions guide us to constantly look for meaning, be it in overheard fragments, mysterious symbols, or life events.</p>
<p>How does this relate to LLMs? For one, they&rsquo;re islands of their own. When you ask them for advice &ndash; to develop a research hypothesis and a matching operationalization, say, or whether a detainee should be released on parole &ndash; they have no stakes in the outcome, no motivation (be it intrinsic or extrinsic), no goals. If an innocent person is harmed, they don&rsquo;t feel the remorse; if an experiment is successful but lacks explanatory power, they don&rsquo;t sense the shallowness; if the world blows up, it won&rsquo;t have been <em>their</em> world.</p>
<p>Secondly, it&rsquo;s us who are <em>not</em> islands. In Bender et al.&rsquo;s octopus scenario, the human on one side of the cable plays an active role not just when they speak. In making sense of what the octopus says, they contribute an essential ingredient: namely, what they think the octopus wants, thinks, feels, expects&hellip; Anticipating, they reflect on what the octopus anticipates.</p>
<p>As Bender et al. put it:</p>
<blockquote>
<p>It is not that O&rsquo;s utterances make sense, but rather, that A can make sense of them.</p>
</blockquote>
<p>That article (Bender and Koller 2020) also brings impressive evidence from human language acquisition: Our predisposition towards language learning notwithstanding, infants don&rsquo;t learn from the availability of input alone. A situation of <em>joint attention</em> is needed for them to learn. Psychologizing, one could hypothesize they need to get the impression that these sounds, these words, and the fact they&rsquo;re linked together, actually matters.</p>
<p>Let me conclude, then, with my final &ldquo;psychologization&rdquo;.</p>
<h3 id="its-us-really-anthropomorphism-unleashed">It&rsquo;s us, <em>really</em>: Anthropomorphism unleashed
</h3>
<p>Yes, it is amazing what these machines do. (And that makes them incredibly dangerous power instruments.) But this in no way affects the human-machine differences that have been existing throughout history, and continue to exist today. That we are inclined to think they understand, know, mean &ndash; that maybe even they&rsquo;re conscious: that&rsquo;s on us. We can experience deep emotions watching a movie; hope that if we just try enough, we can sense what a distant-in-evolutionary-genealogy creature is feeling; see a cloud encouragingly smiling at us; read a sign in an arrangement of pebbles.</p>
<p>Our inclination to anthropomorphize is a gift; but it can sometimes be harmful. And nothing of this is special to the twenty-first century.</p>
<p>Like I began with him, let me conclude with Weizenbaum.</p>
<blockquote>
<p>Some subjects have been very hard to convince that ELIZA (with its present script) is <em>not</em> human.</p>
</blockquote>
<p>Photo by <a 
href="https://unsplash.com/@marjan_blan?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Marjan
Blan</a> on <a 
href="https://unsplash.com/photos/8TLfX3-705M?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></p>
<p>Bender, Emily M., and Alexander Koller. 2020. &ldquo;Climbing Towards NLU: On Meaning, Form, and Understanding in the Age of Data.&rdquo; <em>Proceedings of the 58th Annual Meeting of the Association for Computational Linguistics</em> (Online), July, 5185&ndash;98. <a href="https://doi.org/10.18653/v1/2020.acl-main.463" target="_blank" rel="noopener">https://doi.org/10.18653/v1/2020.acl-main.463</a>
.</p>
<p>Caliskan, Aylin, Pimparkar Parth Ajay, Tessa Charlesworth, Robert Wolfe, and Mahzarin R. Banaji. 2022. &ldquo;Gender Bias in Word Embeddings.&rdquo; <em>Proceedings of the 2022 AAAI/ACM Conference on AI, Ethics, and Society</em>, July. <a href="https://doi.org/10.1145/3514094.3534162" target="_blank" rel="noopener">https://doi.org/10.1145/3514094.3534162</a>
.</p>
<p>Habernal, Ivan, Henning Wachsmuth, Iryna Gurevych, and Benno Stein. 2018. &ldquo;The Argument Reasoning Comprehension Task: Identification and Reconstruction of Implicit Warrants.&rdquo; <em>Proceedings of the 2018 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long Papers)</em> (New Orleans, Louisiana), June, 1930&ndash;40. <a href="https://doi.org/10.18653/v1/N18-1175" target="_blank" rel="noopener">https://doi.org/10.18653/v1/N18-1175</a>
.</p>
<p>Hochreiter, Sepp, and Jürgen Schmidhuber. 1997. &ldquo;Long Short-Term Memory.&rdquo; <em>Neural Computation</em> 9 (December): 1735&ndash;80. <a href="https://doi.org/10.1162/neco.1997.9.8.1735" target="_blank" rel="noopener">https://doi.org/10.1162/neco.1997.9.8.1735</a>
.</p>
<p>Niven, Timothy, and Hung-Yu Kao. 2019. &ldquo;Probing Neural Network Comprehension of Natural Language Arguments.&rdquo; <em>CoRR</em> abs/1907.07355. <a href="http://arxiv.org/abs/1907.07355" target="_blank" rel="noopener">http://arxiv.org/abs/1907.07355</a>
.</p>
<p>Spaerck, Karen. 2004. &ldquo;Language Modelling&rsquo;s Generative Model : Is It Rational?&rdquo;</p>
<p>Vaswani, Ashish, Noam Shazeer, Niki Parmar, et al. 2017. <em>Attention Is All You Need</em>. <a href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener">https://arxiv.org/abs/1706.03762</a>
.</p>
<p>Weizenbaum, Joseph. 1966. &ldquo;ELIZA - a Computer Program for the Study of Natural Language Communication Between Man and Machine.&rdquo; <em>Commun. ACM</em> (New York, NY, USA) 9 (1): 36&ndash;45. <a href="https://doi.org/10.1145/365153.365168" target="_blank" rel="noopener">https://doi.org/10.1145/365153.365168</a>
.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Evidently, this is not about singling out ChatGPT as opposed to other chatbots; rather, I&rsquo;m adopting it as the prototypical such application, since it is the one omnipresent in the media these days.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I&rsquo;m using quotes to refer to how attention is <em>operationalized in deep learning</em>, as opposed to how it is conceptualized in cognitive science or psychology.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>If you&rsquo;re wondering how that is possible &ndash; shouldn&rsquo;t there be a separate, top-level module for generation? &ndash; no, there need not be. That&rsquo;s because training <em>implies</em> prediction.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Why the quotes? See <a href="#large-language-models-what-they-are-not">Large Language Models: What they are not</a>
.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>As a fascinating example from dynamical systems theory, take <a href="https://en.wikipedia.org/wiki/Takens%27s_theorem" target="_blank" rel="noopener">delay coordinate embeddings</a>
.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Suitably named <em>embedding layer.</em>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>See, for example, (Caliskan et al. 2022).&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>For GPT-4, even high-level model information has not been released.&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Mathematically, this is achieved by a pretty standard and pervasively-used, in machine learning, operation, the <em>dot product</em>.&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>&hellip; and the Boltzmann constant &ndash; but that being a constant, we don&rsquo;t consider it here.&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>That choice of species is probably not a coincidence: see <a href="https://en.wikipedia.org/wiki/Cephalopod_intelligence" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Cephalopod_intelligence</a>
.&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>As opposed to the aforementioned problems subsumed under &ldquo;reasoning&rdquo;, those having been constructed for research purposes.&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p>From (Spaerck 2004).&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p>See <a href="https://lisafeldmanbarrett.com/books/how-emotions-are-made/" target="_blank" rel="noopener">https://lisafeldmanbarrett.com/books/how-emotions-are-made/</a>
.&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://posit-open-source.netlify.app/blog/ai/keydanallm/thumbnail.jpg" length="100180" type="image/jpeg" />
    </item>
  </channel>
</rss>
