<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Tidy design principles]]></title><description><![CDATA[Semi-regular discussions of best practices of programming in R]]></description><link>https://tidydesign.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!hPvv!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe36b9a2-1334-45ef-84ec-f0333967d6fa_512x512.png</url><title>Tidy design principles</title><link>https://tidydesign.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 21 Jun 2026 11:50:52 GMT</lastBuildDate><atom:link href="https://tidydesign.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Hadley Wickham]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[tidydesign@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[tidydesign@substack.com]]></itunes:email><itunes:name><![CDATA[Hadley Wickham]]></itunes:name></itunes:owner><itunes:author><![CDATA[Hadley Wickham]]></itunes:author><googleplay:owner><![CDATA[tidydesign@substack.com]]></googleplay:owner><googleplay:email><![CDATA[tidydesign@substack.com]]></googleplay:email><googleplay:author><![CDATA[Hadley Wickham]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[A coding agent is six functions in a trenchcoat]]></title><description><![CDATA[Coding agents like Claude Code, Cursor, and Codex have taken the software engineering field by storm. What makes them tick?]]></description><link>https://tidydesign.substack.com/p/a-coding-agent-is-six-functions-in</link><guid isPermaLink="false">https://tidydesign.substack.com/p/a-coding-agent-is-six-functions-in</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 19 Jun 2026 16:18:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d4ec367c-4e46-49e9-aa56-f3df72a5fa78_2912x1440.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Coding agents like Claude Code, Cursor, and Codex have taken the software engineering field by storm. Since November 2025 they have radically changed the practice of software development for many programmers (including me), and in this week&#8217;s post I want to dive into what makes them tick.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_tkQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_tkQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!_tkQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!_tkQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!_tkQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_tkQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png" width="350" height="350" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:350,&quot;bytes&quot;:541813,&quot;alt&quot;:&quot;A robot in a trenchcoat&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://tidydesign.substack.com/i/202740029?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A robot in a trenchcoat" title="A robot in a trenchcoat" srcset="https://substackcdn.com/image/fetch/$s_!_tkQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!_tkQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!_tkQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!_tkQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5ad415f1-d75b-4d8f-84ce-7ef54bc2565d_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We&#8217;ve now talked about tools (functions) and harnesses (a system for running tools on behalf of the LLM). And you know that an agent is a harness with tools for reading and writing. So what makes an agent a <strong>coding</strong> agent? The answer is the specific tools that it supplies: ones that let an LLM explore and edit a codebase the same way a human would.</p><p>Most coding agents provide some variation on the following tools:</p><ul><li><p><strong>Read file</strong>: read the contents of a file, or a subset of one.</p></li><li><p><strong>Write file</strong>: create a new file with fresh content.</p></li><li><p><strong>Edit file</strong>: make a targeted change to an existing file, usually by replacing one chunk of text with another.</p></li><li><p><strong>List files</strong>: show the files and directories at a given path so the agent can orient itself in the project structure.</p></li><li><p><strong>Search</strong>: find lines matching a pattern across the codebase so the agent can locate relevant code quickly.</p></li><li><p><strong>Run command</strong>: execute an arbitrary shell command, which allows the agent to do anything else it might need.</p></li></ul><p>A good coding agent also usually contains a big prompt with advice about good practices. While these prompts are not open source, they are fairly easy to sniff out, so <a href="https://github.com/Piebald-AI/claude-code-system-prompts">various folks</a> have extracted them. It&#8217;s informative to take a look just to get a sense of how complex they are.</p><h2><strong>Building a minimal coding agent</strong></h2><p>Of the six tools above, only three are really essential for a basic coding agent. To prove it, I&#8217;m going to build a tiny coding agent in R with ellmer. We&#8217;ll start ruthlessly minimal &#8212; with just read file, write file, and run command &#8212; and then work our way up. We&#8217;ll lose some niceties, but in exchange we get something you can read in one sitting.</p><p>First, the three tool functions. I&#8217;ve written them with deliberately boring R:</p><pre><code><code>read_file &lt;- function(path) {
  paste(readLines(path), collapse = "\n")
}

write_file &lt;- function(path, content) {
  writeLines(content, path)
  paste0("Wrote ", path, ".")
}

run_command &lt;- function(command) {
  paste(system(command, intern = TRUE), collapse = "\n")
}</code></code></pre><p>Each function returns a string which is fed back to the LLM as the result of the tool call: <code>read_file</code> returns the file&#8217;s contents, <code>write_file</code> confirms what it did, and <code>run_command</code> returns whatever the command printed to the console.</p><p>Next we create a chat and register the three functions as tools. As before, the descriptions matter: they tell the LLM when and how to reach for each tool. We also include a very basic prompt.</p><pre><code><code>library(ellmer)
#&gt; Warning: replacing previous import 'S7:::=' by 'rlang:::=' when loading
#&gt; 'ellmer'
chat &lt;- chat_anthropic(
  system_prompt = "
    You are a coding assistant working in the user's current directory.
    Use the tools to explore and modify their project. Before editing a
    file, read it first. After making changes, run any relevant tests or
    checks to confirm your work. Keep going until the task is done.
  "
)
#&gt; Using model = "claude-sonnet-4-5-20250929".

chat$register_tool(tool(
  read_file,
  description = "Read the entire contents of a text file.",
  arguments = list(
    path = type_string("Path to the file, relative to the working directory.")
  )
))

chat$register_tool(tool(
  write_file,
  description = "Write content to a file, overwriting it if it already exists.
    Always read the file first if you only want to change part of it.",
  arguments = list(
    path = type_string("Path to the file, relative to the working directory."),
    content = type_string("The full new contents of the file.")
  )
))

chat$register_tool(tool(
  run_command,
  description = "Run a shell command and return its output. Use this to list
    files (ls), search code (grep), run tests, or use git.",
  arguments = list(
    command = type_string("The shell command to run.")
  )
))</code></code></pre><p>And that&#8217;s the whole agent!</p><p>The shell tool is our &#8220;get out of jail free&#8221; card because if you can run a shell command you can do anything: you can call <code>ls</code> to list directories, <code>grep</code> to search for code, <code>Rscript</code> to run R code, and <code>git</code> for git. (Technically we don&#8217;t even need read and write tools because the agent could use <code>echo</code> and <code>cat</code>, but we&#8217;re going to throw the agent a bone here.) The shell tool is also rather dangerous; we&#8217;ll come back to that later.</p><p>You can now use this agent for a simple task:</p><pre><code><code>chat$chat("Find the function that parses dates and add a unit test for it.")</code></code></pre><p>Behind the scenes the model might use <code>run_command</code> to <code>grep</code> for the function then <code>read_file</code> to study it. Next, to create the test, it will need to read the existing test files with <code>read_file</code>, then <code>write_file</code> to add the new test, and finish up by using <code>run_command</code> to run the test suite. It&#8217;s no Claude Code, but it&#8217;s in the same galaxy.</p><h2><strong>Finding files: list and search</strong></h2><p>Our minimal agent can already list and search files via <code>run_command</code>, but leaning on the shell for everything has downsides. Shell commands vary between platforms (Windows doesn&#8217;t have <code>ls</code> or <code>grep</code>) and their output is noisy. More importantly, handing the model a general-purpose shell is a security nightmare: it&#8217;s very difficult to tell if a specific shell command is safe or dangerous. It&#8217;s much easier to add safeguards to stricter tools.</p><p>Let&#8217;s see what that might look like by implementing list and search tools, then seeing how we could make them safer.</p><pre><code><code>list_files &lt;- function(path = ".") {
  paste(list.files(path), collapse = "\n")
}

search_files &lt;- function(pattern, path = ".") {
  files &lt;- list.files(path, recursive = TRUE, full.names = TRUE)
  hits &lt;- character()
  for (file in files) {
    lines &lt;- readLines(file, warn = FALSE)
    matches &lt;- grep(pattern, lines)
    hits &lt;- c(hits, paste0(file, ":", matches, ": ", lines[matches]))
  }
  paste(hits, collapse = "\n")
}</code></code></pre><p>And then add them to our harness:</p><pre><code><code>chat$register_tool(tool(
  list_files,
  description = "List the files and directories at a given path.",
  arguments = list(
    path = type_string("Directory to list. Defaults to the working directory.")
  )
))

chat$register_tool(tool(
  search_files,
  description = "Search the contents of all files for a regular expression,
    returning matching lines as 'path:line: text'.",
  arguments = list(
    pattern = type_string("A regular expression to search for."),
    path = type_string("Directory to search. Defaults to the working directory.")
  )
))</code></code></pre><h2><strong>Keeping it safe</strong></h2><p>These list and search tools are easier to make safe than a general shell, because they have simpler inputs. But they&#8217;re not automatically safe: there&#8217;s nothing stopping the model passing <code>path = ".."</code> or an absolute path like <code>/etc</code>, so our &#8220;search the project&#8221; tool could happily read your entire hard drive, including your passwords.</p><p>Let&#8217;s make them safer by checking that paths stay within the project directory. We resolve the path (which collapses <code>..</code> and follows symlinks) and check that it still sits underneath the working directory:</p><pre><code><code>safe_path &lt;- function(path) {
  root &lt;- normalizePath(".", winslash = "/", mustWork = TRUE)
  full &lt;- normalizePath(path, winslash = "/", mustWork = FALSE)
  within_root &lt;- full == root || startsWith(full, paste0(root, "/"))

  if (!within_root) {
    stop("Path is outside the project directory: ", path)
  }
  path
}</code></code></pre><p>It&#8217;s also worth verifying that they can&#8217;t read the <code>.Renviron</code> file, where R programmers conventionally stash API keys and other secrets. Luckily we get that for free: <code>list.files()</code> defaults to <code>all.files = FALSE</code>, which skips dotfiles, so <code>.Renviron</code>, <code>.Rhistory</code>, and the contents of <code>.git/</code> are already excluded from our search.</p><p>Putting both together, <code>search_files</code> becomes:</p><pre><code><code>search_files &lt;- function(pattern, path = ".") {
  files &lt;- list.files(safe_path(path), recursive = TRUE, full.names = TRUE)

  hits &lt;- character()
  for (file in files) {
    lines &lt;- readLines(file, warn = FALSE)
    matches &lt;- grep(pattern, lines)
    hits &lt;- c(hits, paste0(file, ":", matches, ": ", lines[matches]))
  }
  paste(hits, collapse = "\n")
}</code></code></pre><p>The same <code>safe_path()</code> wrapper belongs on <code>read_file</code>, <code>write_file</code>, and <code>list_files</code> too; otherwise the model can still reach outside the project by reading or writing a file directly.</p><h2><strong>Making it efficient</strong></h2><p>The biggest weakness of our minimal agent is that the only way to change a file is to rewrite it from scratch with <code>write_file</code>. For a 500-line file where you want to change one line, that means the model has to reproduce all 500 lines perfectly &#8212; slow, expensive, and challenging for today&#8217;s models. The fix is a targeted edit tool that swaps one chunk of text for another:</p><pre><code><code>edit_file &lt;- function(path, old, new) {
  path &lt;- safe_path(path)
  content &lt;- paste(readLines(path), collapse = "\n")

  if (!grepl(old, content, fixed = TRUE)) {
    stop("Could not find the text to replace in ", path, ".")
  }

  content &lt;- sub(old, new, content, fixed = TRUE)
  writeLines(content, path)
  paste0("Edited ", path, ".")
}</code></code></pre><pre><code><code>chat$register_tool(tool(
  edit_file,
  description = "Replace an exact span of text in a file with new text. The
    'old' text must appear exactly once in the file. Prefer this over
    write_file for changing part of an existing file.",
  arguments = list(
    path = type_string("Path to the file to edit."),
    old = type_string("The exact existing text to replace."),
    new = type_string("The text to replace it with.")
  )
))</code></code></pre><p>This is useful for two reasons. First, the model only has to write the few lines that change, not the whole file, so it&#8217;s both faster and cheaper. Second, it&#8217;s much safer: because the <code>old</code> text has to match exactly, a botched edit fails loudly instead of silently corrupting the file. This is exactly how the edit tools in real coding agents work, give or take some cleverness with whitespace, safeguards against multiple matches, and detecting when a human is also editing the file.</p><p>Next we&#8217;ll we come back to the topic of security in more detail, and explore the approaches you can use to make general tools (like running a shell command) as safe as possible.</p>]]></content:encoded></item><item><title><![CDATA[Your LLM can’t math (and that’s ok)]]></title><description><![CDATA[Last week we precisely defined an agent: an LLM, in a harness, that calls tools repeatedly in a loop. This week we'll fix a few deliberate simplifications and explore the consequences of the harness in a bit more detail.]]></description><link>https://tidydesign.substack.com/p/your-llm-cant-math-and-thats-ok</link><guid isPermaLink="false">https://tidydesign.substack.com/p/your-llm-cant-math-and-thats-ok</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 12 Jun 2026 13:18:11 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e5ba8b38-e7c8-4abc-82db-1ddcfcf5ac9c_2912x1440.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last week we precisely defined an agent: an LLM, in a harness, that calls tools repeatedly in a loop. Most agents have two types of tools: read tools that can observe the world, and write tools that can change the world.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XFUB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XFUB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!XFUB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!XFUB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!XFUB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XFUB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png" width="360" height="360" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:360,&quot;bytes&quot;:782037,&quot;alt&quot;:&quot;A retro robot wearing a harness and dancing in a club&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://tidydesign.substack.com/i/201743556?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A retro robot wearing a harness and dancing in a club" title="A retro robot wearing a harness and dancing in a club" srcset="https://substackcdn.com/image/fetch/$s_!XFUB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!XFUB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!XFUB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!XFUB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff096e11b-3d07-4d09-8832-8ac0657c77b1_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>But my definition of tools contained a deliberate simplification that I want to dig into this week. Previously, I said tools run on your computer, but that&#8217;s not generally true: tools are run by the harness. In many cases (like with ellmer or a coding agent), the harness runs locally, but when using web chat (like claude.ai or chatgpt.com), the harness runs on the server.</p><h2><strong>Multiplying numbers</strong></h2><p>Let&#8217;s explore what that means, starting with a simple example using a bare ellmer chat with no tools. What happens if we ask it to multiply two large numbers, a task that we know LLMs are not good at?</p><pre><code><code>library(ellmer)

x &lt;- 20598162
y &lt;- 83106206

chat &lt;- chat_openai()
chat$chat(interpolate("What's {{x}} * {{y}}?"))
#&gt; The product of **20,598,162** and **83,106,206** is:
#&gt; 
#&gt; **1,711,067,239,026,172**</code></code></pre><p>We can check the answer with R, which does know how to multiply correctly. This reveals that the model was confidently wrong:</p><pre><code><code>format(x * y, big.mark = ",", scientific = FALSE)
#&gt; [1] "1,711,835,094,393,372"</code></code></pre><p>However, if you ask this same question on chatgpt.com or claude.ai, they both get it right. Why? Because the web chats are also harnesses that come with a variety of useful tools. One of those tools is a calculator, because both OpenAI and Anthropic know that LLMs are bad at math.</p><p>We can give our ellmer chat a similar tool. First we&#8217;ll implement a basic calculator function that takes a mathematical expression as a string and returns the result:</p><pre><code><code>calculator &lt;- function(code) {
  math_env &lt;- new.env(parent = emptyenv())
  math_env$`+` &lt;- `+`
  math_env$`-` &lt;- `-`
  math_env$`*` &lt;- `*`
  math_env$`/` &lt;- `/`
  math_env$`^` &lt;- `^`
  math_env$`(` &lt;- `(`
  math_env$sqrt &lt;- sqrt
  math_env$exp &lt;- exp
  math_env$log &lt;- log

  eval(parse(text = code), envir = math_env)
}

calculator("15 * 11")
#&gt; [1] 165
calculator("read.csv('foo.bar')")
#&gt; Error in `read.csv()`:
#&gt; ! could not find function "read.csv"</code></code></pre><p>This code uses some environment and evaluation magic (which you can learn more about in <a href="https://adv-r.hadley.nz/meta-big-picture.html#eval-funs">Advanced R</a>) to restrict R to a set of safe operations. In the future, I&#8217;ll talk about why you might want to free this up to run any R code and how you can do that (mostly) safely.</p><p>Next we register the function as a tool. Note that we give some advice to the LLM on when to use it. Like with all LLM prompting, there&#8217;s no guarantee that the LLM will listen, but it&#8217;s likely to help (in the sense that the average quality of responses with the tool should be better than without it).</p><pre><code><code>chat &lt;- chat_openai()
#&gt; Using model = "gpt-4.1".
chat$register_tool(tool(
  calculator,
  description = "Evaluate a mathematical expression.
    Use this for any arithmetic, since you can't reliably compute it yourself.
    Supports +, -, *, /, ^, parentheses, sqrt(), exp(), and log(), following 
    standard precedence rules.",
  arguments = list(
    code = type_string(
      "Mathematical expression to evaluate, e.g. '1 + 2 * 3'."
    )
  )
))</code></code></pre><p>Now if we re-ask the same question we get the right result!</p><pre><code><code>chat$chat(interpolate("What's {{x}} * {{y}}?"))
#&gt; &#9711; [tool call] calculator(code = "20598162 * 83106206")
#&gt; &#9679; #&gt; 1711835094393372
#&gt; 20598162 multiplied by 83106206 equals 1,711,835,094,393,372.</code></code></pre><h2><strong>Other tools</strong></h2><p>The harnesses used by current chatbots provide the LLM with a whole raft of tools. Here are a few that you might have noticed:</p><ul><li><p><strong>Search the web</strong>: when the model needs information outside of its training data (like something recent or hyperspecific), it can search the web. Here the model writes a search query, the tool runs it and returns the results (titles, snippets, and links) back to the conversation.</p></li><li><p><strong>Fetch a page</strong>: when the model needs to read a webpage, the tool downloads it and returns its contents as text. This often goes hand-in-hand with web search, but is also what lets you paste in a URL and ask the model to summarise it. (I often use this to extract recipes into a format I like: strip the blather at the start, repeat the ingredients needed at each step, and convert to metric measurements.)</p></li><li><p><strong>Make a memory</strong>: when you tell the LLM to remember something, it edits some central markdown file that&#8217;s included in the prompt for all future conversations. This is how an assistant can &#8220;remember&#8221; your name, or that you use British English, or that you prefer sentence case for headings, without you repeating yourself every time.</p></li><li><p><strong>Draw a picture</strong>: when you ask the LLM to generate an image, it doesn&#8217;t draw anything itself but instead acts as a skilled prompt-writer for a separate, specialised model. The model writes a detailed text prompt, uses a tool call to an image model to generate the image, then adds that to the conversation. This is how ChatGPT and Gemini generate images.</p></li></ul><p>Why does it matter that these are all tool calls and not some intrinsic property of the model? Because it shows that even if the underlying model is static, the abilities of the system can grow over time. And importantly, because you can make a harness with your own tools, you can extend the model in whatever way you want. When you combine that insight with tools like <a href="https://posit-dev.github.io/shinychat/">shinychat</a>, you have the potential to create tailored systems that can do better than the best generic chatbots for your specific domain.</p><h2><strong>One last wrinkle</strong></h2><p>There&#8217;s one last wrinkle to cover. I said tool calls were the responsibility of the harness, but that&#8217;s another simplification: there&#8217;s also a selection of built-in tool calls that the models can do themselves. (Or maybe the right way to describe this is that models also come with a limited server-side harness.)</p><p>For example, the big three (OpenAI, Anthropic, and Google) all provide a built-in web search tool call:</p><pre><code><code>chat &lt;- chat_openai("Be terse", model = "gpt-5.4-mini", echo = FALSE)
chat$register_tool(openai_tool_web_search())
. &lt;- chat$chat("When is Hadley Wickham's birthday?")
chat
#&gt; &lt;Chat OpenAI/gpt-5.4-mini turns=3 input=8501 output=72 cost=$0.01&gt;
#&gt; &#9472;&#9472; system &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
#&gt; Be terse
#&gt; &#9472;&#9472; user &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
#&gt; When is Hadley Wickham's birthday?
#&gt; &#9472;&#9472; assistant [input=8501 output=72 cost=$0.01] &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
#&gt; [web search request]: "Hadley Wickham birthday birth date"
#&gt; Hadley Wickham&#8217;s birthday is **October 14, 1979**. ([en.wikipedia.org](https://en.wikipedia.org/wiki/Hadley_Wickham?utm_source=openai))</code></code></pre><p>This is nice because instead of user -&gt; LLM -&gt; harness -&gt; LLM we can eliminate one HTTP request and response, reducing it to user -&gt; LLM (with tool call), saving some time. You can see that in the result above: the final response acknowledges that a web search was performed before including the text response. (And as an added benefit, you now know when to send me a birthday card &#127874;.)</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Tidy design principles! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[What is an agent?]]></title><description><![CDATA[Conversations, turns, rounds, tools, agents and more!]]></description><link>https://tidydesign.substack.com/p/what-is-an-agent</link><guid isPermaLink="false">https://tidydesign.substack.com/p/what-is-an-agent</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 05 Jun 2026 19:50:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/673e6daf-6d99-4e37-ad35-2e642ed68637_1819x701.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I thought it might be useful to talk about what exactly an agent is, because there&#8217;s a lot of mystery hiding behind a fairly straightforward idea. Once you understand that idea, you&#8217;ll better understand how tools like Claude Code work and start to see how you could build your own.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RMZF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RMZF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 424w, https://substackcdn.com/image/fetch/$s_!RMZF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 848w, https://substackcdn.com/image/fetch/$s_!RMZF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!RMZF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RMZF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png" width="440" height="440" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:440,&quot;bytes&quot;:7833591,&quot;alt&quot;:&quot;A mid-century modern retro-futuristic illustration of a square-headed robot dressed as a 1950s secret agent.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://tidydesign.substack.com/i/200800827?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A mid-century modern retro-futuristic illustration of a square-headed robot dressed as a 1950s secret agent." title="A mid-century modern retro-futuristic illustration of a square-headed robot dressed as a 1950s secret agent." srcset="https://substackcdn.com/image/fetch/$s_!RMZF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 424w, https://substackcdn.com/image/fetch/$s_!RMZF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 848w, https://substackcdn.com/image/fetch/$s_!RMZF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!RMZF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c8454c2-b5cc-4dc6-aa84-41f204adc6da_2048x2048.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To understand what an agent is, you first need to understand what a tool call is. And to understand a tool call, you need to understand the basic human &lt;-&gt; LLM conversational loop. So we&#8217;ll start there and then work our way back up.</p><h2><strong>Conversation basics</strong></h2><p>A conversation with an LLM is a sequence of HTTP requests and HTTP responses: you say something then the LLM responds. LLMs (like pretty much all modern web APIs) communicate with JSON. So when you send a request to Claude (my personal favourite LLM), the request body includes some JSON like this:</p><pre><code><code>"role": "user",
"content": [
  {
    "type": "text",
    "text": "Tell me a joke",
  }
]</code></code></pre><p>(I&#8217;ve trimmed these snippets for readability. That means they&#8217;re not valid JSON, but should give you the flavour of what&#8217;s going on under the hood.)</p><p>This message is from the user (me, the human) and includes <em>text</em> asking Claude to tell me a joke. Then Claude sends a message back:</p><pre><code><code>"role": "assistant",
"content": [
  {
    "type": "text",
    "text": "Why don't scientists trust atoms?\n\nBecause they make up everything!"
  }
]</code></code></pre><p>This message is from the assistant (Claude) and gives the joke I requested in text format.</p><p>We call each of these two messages a <strong>turn</strong>. Turns always come in pairs and always happen in the same order: user then assistant. The LLM will always listen to everything you have to say, and only then respond. (This is something we can all strive for in our own conversations &#128518;.)</p><p>It&#8217;s worth noting that the LLM API is stateless. That means if we continue this conversation, we have to resend the entire conversation so far:</p><pre><code><code>messages: [
  {
    "role": "user",
    "content": [
      {
        "type": "text",
        "text": "Tell me a joke",
      }
    ]
  },
  {
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "Why don't..."
      }
    ]
  }
]</code></code></pre><p>This is why token costs grow over the course of a conversation; each new request has to include the contents of all the previous turns.</p><h2><strong>Tools</strong></h2><p>Things get more complicated when we introduce tools. Tools are a way for LLMs to break free from their limits: they allow LLMs to get data about the world as it is today and allow them to take actions. But what exactly is a tool? A <strong>tool</strong> is just a different name for a function, but importantly it&#8217;s a function that&#8217;s run on your computer.</p><p>Let&#8217;s see how that works in ellmer by registering a very simple tool:</p><pre><code><code>chat &lt;- chat_claude(model = "claude-sonnet-4-5")
chat$register_tool(tool(
  \() Sys.Date(),
  name = "today",
  description = "Get today's date"
))</code></code></pre><p>Now we can use the tool to allow Claude to answer a question that cannot be part of its training data: today&#8217;s date. So our request includes a description of that tool:</p><pre><code><code>"tools": [
  {
    "name": "today",
    "description": "Get today's date",
    "input_schema": {
      "type": "object",
      "description": "",
    }
  }, 
]</code></code></pre><p>(This is everything the model knows about the tool, which is why writing a good description is important for more complex tools.)</p><p>Then the request continues as before:</p><pre><code><code>"role": "user",
"content": [
  {
    "type": "text",
    "text": "What day is it today?",
  }
]</code></code></pre><p>Claude can&#8217;t respond immediately because it doesn&#8217;t know the answer. So instead it responds with type &#8220;tool_use&#8221;:</p><pre><code><code>"role": "assistant",
"content": [
  {
    "type": "tool_use",
    "id": "toolu_012g5Pv3hqogjmTSWFpwPPnE",
    "name": "today",
    "input": {}
  }
]</code></code></pre><p>This is a request for you to do some work, i.e. call the <code>today()</code> function with no arguments. This workflow wouldn&#8217;t be very appealing if you personally had to call this function, which is where the harness comes in. A <strong>harness</strong> is a program that wraps around the raw LLM and can (among other things) run these local functions for you. Harnesses include the web chat interface, more complicated tools like Claude Code or Codex, and in this case, ellmer.</p><p>So now ellmer takes over, sending a new message back to the assistant that contains the results of calling <code>today()</code>. It also includes the complete prior conversational history, but I&#8217;ve omitted that to stay focused on what&#8217;s changed:</p><pre><code><code>"role": "user",
"content": [
  {
    "type": "tool_result",
    "tool_use_id": "toolu_012g5Pv3hqogjmTSWFpwPPnE",
    "content": "2026-06-01",
    "is_error": false
  }
]</code></code></pre><p>(The role here is a little confusing; it would be clearer to say that this response was generated by the harness, rather than the user.)</p><p>Now that Claude knows what day it is, it can respond to our initial query:</p><pre><code><code>"role": "assistant",
"content": [
  {
    "type": "text",
    "text": "Today is **Monday, June 1st, 2026**."
  }
]</code></code></pre><p>We call the complete sequence of human, harness, and LLM turns a <strong>round</strong>, which in this case consists of four turns/two pairs (human -&gt; LLM, harness -&gt; LLM).</p><h2><strong>So what&#8217;s an agent?</strong></h2><p>With all these pieces in place we can now define an agent. An <strong>agent</strong> is an LLM, in a harness, that calls tools repeatedly in a loop, deciding each next step from the last result. Most agents have two types of tools: <strong>read</strong> tools that can observe the world, and <strong>write</strong> tools that can change the world. This combination leads to a natural iterative cycle where the LLM does some initial exploration (read), makes a change (write), observes the result of the change (read), etc etc. That&#8217;s why our example above wasn&#8217;t an agent; there&#8217;s no need for iteration.</p><p>So now let&#8217;s make a real, if simple, agent. The goal of this agent is to help you delete files. So we give it a read tool that lists the files in the current directory and a write tool that deletes files:</p><pre><code><code>chat &lt;- chat_claude(model = "claude-sonnet-4-5")
chat$register_tool(tool(
  function() dir(),
  name = "ls",
  description = "Lists the files in the current directory"
))
chat$register_tool(tool(
  function(path) unlink(path),
  name = "rm",
  description = "Delete a file",
  arguments = list(path = type_string())
))</code></code></pre><p>Now we can use the agent to effect change in the world:</p><pre><code><code>chat$chat("Delete all the csv files in the current directory")</code></code></pre><p>I won&#8217;t show the full sequence of JSON requests and responses here because it&#8217;s a bit long, but in summary it looks like this:</p><ul><li><p>User: Delete all the csv files in the current directory.</p></li><li><p>Claude: Run the <code>ls()</code> tool</p></li><li><p>ellmer: <code>a.csv</code>, <code>b.csv</code>, <code>a.R</code>, <code>b.R</code>, <code>c.R</code></p></li><li><p>Claude: [Run <code>rm("a.csv")</code>, Run <code>rm("b.csv")</code>]</p></li><li><p>ellmer: [<code>TRUE</code>, <code>TRUE</code>]</p></li><li><p>Claude: I&#8217;ve deleted two csv files for you.</p></li></ul><p>This is one round made up of six turns/three pairs (human-LLM, harness-LLM, harness-LLM).</p><p>Now let&#8217;s talk about the elephant in the room: I just gave an LLM the ability to delete files on my computer! Hopefully you find this a little worrying, as we know that LLMs are never 100% trustworthy.</p><p>This is one of the biggest challenges of agents. An agent isn&#8217;t useful unless it can take actions on your behalf, but how do you know it&#8217;s always taking the right actions? This is one of the biggest questions posed by AI agents. There are some things you can do locally to protect yourself when the agent is scoped to actions on your computer, like sandboxing, using git, and making backups. But what if actions are in the real world like sending emails or spending money? That feels high risk to me!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://tidydesign.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>As usual, I&#8217;d love to know how this article landed with you. Did you learn something new or is this old hat? What questions are you left wondering about?</p>]]></content:encoded></item><item><title><![CDATA[Returning to life!]]></title><description><![CDATA[Welcome back to the tidy design principles substack.]]></description><link>https://tidydesign.substack.com/p/returning-to-life</link><guid isPermaLink="false">https://tidydesign.substack.com/p/returning-to-life</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Mon, 18 May 2026 13:55:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Qn7c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back to the tidy design principles substack. I&#8217;m bringing it back to life to talk about what I&#8217;m thinking most about these days: AI. There are so many voices talking about AI, and I feel ambivalent about adding one more. And frankly, I have a lot more questions than answers. But writing and listening are my keys of tools for understanding a new domain, so I hope you&#8217;ll join be on this journey.</p><p>In future posts, I&#8217;ll get more technical, but I wanted to begin by acknowledging your likely deeply conflicted feelings about AI.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qn7c!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qn7c!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 424w, https://substackcdn.com/image/fetch/$s_!Qn7c!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 848w, https://substackcdn.com/image/fetch/$s_!Qn7c!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!Qn7c!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qn7c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png" width="396" height="396" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1456,&quot;width&quot;:1456,&quot;resizeWidth&quot;:396,&quot;bytes&quot;:7467703,&quot;alt&quot;:&quot;A mid-century modern gouache illustration of a conflicted, 1950s-style robot with a sad expression, holding one hand to its square head and the other to its chest. The robot is built from primitive geometric forms and stands against a minimalist, abstract background of geometric shapes and arches. The artwork features a tactile, painterly texture and a muted vintage color palette of terracotta, mustard yellow, olive green, and slate blue on a cream backdrop.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://tidydesign.substack.com/i/198255005?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A mid-century modern gouache illustration of a conflicted, 1950s-style robot with a sad expression, holding one hand to its square head and the other to its chest. The robot is built from primitive geometric forms and stands against a minimalist, abstract background of geometric shapes and arches. The artwork features a tactile, painterly texture and a muted vintage color palette of terracotta, mustard yellow, olive green, and slate blue on a cream backdrop." title="A mid-century modern gouache illustration of a conflicted, 1950s-style robot with a sad expression, holding one hand to its square head and the other to its chest. The robot is built from primitive geometric forms and stands against a minimalist, abstract background of geometric shapes and arches. The artwork features a tactile, painterly texture and a muted vintage color palette of terracotta, mustard yellow, olive green, and slate blue on a cream backdrop." srcset="https://substackcdn.com/image/fetch/$s_!Qn7c!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 424w, https://substackcdn.com/image/fetch/$s_!Qn7c!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 848w, https://substackcdn.com/image/fetch/$s_!Qn7c!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 1272w, https://substackcdn.com/image/fetch/$s_!Qn7c!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2c73a860-3459-40e0-9dda-9f49876f5085_2048x2048.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A conflicted robot drawn by nanobanana2</figcaption></figure></div><div><hr></div><p>Let me start with the bits of AI that I feel genuinely excited about:</p><p><strong>Programming accessibility</strong>. There are tons of people who could benefit from a programming language like R, but can&#8217;t justify the investment in learning it. AI has dramatically lowered that barrier and you can now get many of the benefits of reproducible programming with R much faster than you could before. Similarly, effective usage of git is now within reach to a much broader audience.</p><p><strong>Translation</strong>. While machine translations are still far from perfect, their quality has improved radically in the last few years. This means that much more of the programming ecosystem is now available to the majority of the world who are not fluent English speakers.</p><p><strong>Voice input</strong>. Voice input is a super exciting technology because it means that you no longer need to be a fluent touch typist in order to quickly get your thoughts into a computer. (Not to mention making a lot more technology available if you can&#8217;t read or write.) That&#8217;s a meaningful expansion of who gets to participate in technology.</p><p><strong>Wide and shallow expertise.</strong> I love Tukey&#8217;s quote that statisticians get to play in everyone&#8217;s backyard. And it&#8217;s now easier than ever thanks to AI. AI will not make you an expert but can give you <em>shallow</em> expertise in basically anything you&#8217;re curious about. I think that&#8217;s pretty cool.</p><p>Finally, I have found AI to be a tremendous accelerator in my own work. It&#8217;s allowed me to fix 100s of issue in core infrastructure packages like <a href="https://opensource.posit.co/blog/2026-05-01_roxygen2-8-0-0/">roxygen2</a> and <a href="https://tidyverse.org/blog/2025/11/testthat-3-3-0/">testthat</a>. This is not AI slop; this is carefully vetted code that I can now write ~2-5x faster than I could before.</p><div><hr></div><p>But you can&#8217;t use AI without also considering the harms, of which there are many.</p><p><strong>Environmental impact</strong>. At the individual level, I believe that if you want to reduce your environmental footprint, there are higher-leverage changes that you can make. But at the societal level, the picture is more concerning: the rush to create new data centers is increasing need for electricity and water, and leading companies to rollback their climate commitments.</p><p><strong>Copyright theft</strong>. LLMs are trained on vast quantities of copyrighted material, taken at an unprecedented and industrial scale, without permission or compensation.</p><p><strong>Concentration of wealth</strong>. The AI craze is pushing more and more money into the hands of fewer and fewer people. I find the concentration of wealth and power into the hands of a very small number of people to be genuinely disturbing and I think is something that we should all be concerned about.</p><p><strong>Intellectual laziness</strong>. AI supports a kind of shallow engagement where you never have to strain your brain on any task. The path of least resistance is to disengage and just let the model handle it. You no longer have to experience any mental discomfort, and thus you never really <em>learn</em>.</p><p><strong>Equity and access</strong>. I&#8217;ve built my career around open source software, and one of the things I love about it is that it&#8217;s available to everyone, everywhere in the world, regardless of their means. That&#8217;s not possible with AI. The best tools cost real money, usually charged in US dollars, and that makes them out of reach for a lot of people in a lot of places.<br></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Tidy design principles! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><p>How do you resolve the tension between the empowering and harmful parts of AI? I wish I could give you an answer. All I can suggest is to sit with this conflict. Acknowledge that it&#8217;s complicated and there are no easy answers. And do your best to ignore the AI boosters and AI doomers who want to make it easy.</p><p>Why am <em>I</em> engaging so heavily with AI? Firstly, I see my job as broadly empowering data scientists. If data scientists are now using AI, then it&#8217;s my job to look at what they&#8217;re doing and see if I can help them to do it better. Secondly, it feels crucial for the future of Posit. We&#8217;re a successful company and doing well, but if we don&#8217;t seriously engage with AI then I don&#8217;t think we can survive. And while I&#8217;m admittedly biased, I do think the failure of Posit would be a loss for the world since we invest so much into free and open source tools.</p><p>So that&#8217;s why I&#8217;m bringing this substack back to life to talk about AI. But I want to know how I can best serve you. What are your concerns about using AI for data science? Where are you seeing successes and failures? What do you want to learn about? Please let me know in the comments, or if you&#8217;d like a deeper discussion you can <a href="https://scheduler.zoom.us/hadley-wickham/ai-chat">find a time to chat with me</a><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I&#8217;m doing six of these calls a week, and new slots will be opening up regularly.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Strategies]]></title><description><![CDATA[Exposing different ways to tackle a problem in a user friendly way.]]></description><link>https://tidydesign.substack.com/p/strategies</link><guid isPermaLink="false">https://tidydesign.substack.com/p/strategies</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 27 Oct 2023 13:41:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!p880!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!p880!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!p880!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!p880!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!p880!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!p880!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!p880!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg" width="356" height="356" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:356,&quot;bytes&quot;:224240,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!p880!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!p880!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!p880!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!p880!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5140da9d-35ab-4ef3-9f54-b0c066018f4b_1024x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Painting of a robot looking at a battle strategy map in cute retro style, made with Bing Image Creator.</figcaption></figure></div><p>Over the last couple of weeks I&#8217;ve been noodling on the idea of strategies: what happens if your function contains a few different approaches to the problem, and you want to expose to the user select. I believe that it&#8217;s best to expose these explicitly like the&nbsp;<code>ties.method</code>&nbsp;argument to&nbsp;<code>rank()</code>, the&nbsp;<code>method</code>&nbsp;argument to&nbsp;<code>p.adjust()</code>, or the&nbsp;<code>.keep</code>&nbsp;argument to&nbsp;<code>dplyr::mutate()</code>.</p><p>In functions that expose a strategy, it&#8217;s common to see a character vector in the function interface. For example,&nbsp;<code>rank()</code>&nbsp;looks like this:</p><pre><code><code>rank(
  x,
  na.last = TRUE,
  ties.method = c("average", "first", "last", "random", "max", "min")
)</code></code></pre><p>I call this character vector an&nbsp;<strong>enumeration</strong>&nbsp;and discuss it in &#8220;<a href="https://design.tidyverse.org/enumerate-options.html">Enumerate possible options</a>&#8221;. This vector enumerates (itemises) the possible options (six here) and it also tells you the default value, the first value in the vector (<code>"average"</code>). This type of default value is usually paired with either&nbsp;<code>match.arg()</code>&nbsp;or&nbsp;<code>rlang::arg_match()</code>&nbsp;to give an informative error if the user supplies an unsupported value. The chief difference between the base and rlang versions of this function is that the base version supports partial matching (e.g.&nbsp;you can write&nbsp;<code>rank(x, ties.method = "r")</code>&nbsp;for short), which we believe is no longer a good idea.</p><p>One of the reasons it&#8217;s useful to understand this pattern is that you might want to apply it even when there are only two options. In such a case, it&#8217;s tempting to expose the option as a Boolean argument, accepting either&nbsp;<code>TRUE</code>&nbsp;or&nbsp;<code>FALSE</code>. But this has two problems:</p><ul><li><p>You might later discover that there&#8217;s a third option. Now you&#8217;re going to need to make more radical changes to your function interface to allow this.</p></li><li><p>It&#8217;s often trickier to understand a negative. For example, I recently discovered the&nbsp;<code>cancel_on_error</code>&nbsp;argument in an&nbsp;<a href="https://httr2.r-lib.org/">httr2</a>&nbsp;function. I think it&#8217;s pretty clear what&nbsp;<code>cancel_on_error = TRUE</code>&nbsp;does (it cancels if there&#8217;s an error), but what does&nbsp;<code>cancel_on_error = FALSE</code>&nbsp;do? I wrote this code and now I couldn&#8217;t tell you what it actually does.</p></li></ul><p>I explore this idea in more detail in &#8220;<a href="https://design.tidyverse.org/boolean-strategies.html">Prefer a enum, even if only two choices</a>&#8221;, including a deeper look at the&nbsp;<code>decreasing</code>&nbsp;and&nbsp;<code>na.last</code>&nbsp;arguments to&nbsp;<code>sort()</code>.</p><p>A more complicated example of the strategy pattern comes about when different strategies require different arguments. The best example of this sort of pattern is stringr, which uses the functions&nbsp;<code>regex()</code>,&nbsp;<code>fixed()</code>,&nbsp;<code>boundary()</code>, and&nbsp;<code>coll()</code>&nbsp;to define the pattern matching engine:</p><pre><code><code>library(stringr)
x &lt;- "The quick brown fox jumped over the lazy dog."

str_view(x, regex("[aeiou]+", ignore_case = TRUE))
#&gt; [1] &#9474; Th&lt;e&gt; q&lt;ui&gt;ck br&lt;o&gt;wn f&lt;o&gt;x j&lt;u&gt;mp&lt;e&gt;d &lt;o&gt;v&lt;e&gt;r th&lt;e&gt; l&lt;a&gt;zy d&lt;o&gt;g.
str_view(x, fixed("."))
#&gt; [1] &#9474; The quick brown fox jumped over the lazy dog&lt;.&gt;
str_view(x, boundary("word"))
#&gt; [1] &#9474; &lt;The&gt; &lt;quick&gt; &lt;brown&gt; &lt;fox&gt; &lt;jumped&gt; &lt;over&gt; &lt;the&gt; &lt;lazy&gt; &lt;dog&gt;.</code></code></pre><p>I explore this idea more in &#8220;<a href="https://design.tidyverse.org/strategy-objects.html">Extract strategies into objects</a>&#8221; (which really needs a catchier name), motivated by the&nbsp;<code>perl</code>,&nbsp;<code>fixed</code>, and&nbsp;<code>ignore.case</code>&nbsp;arguments to&nbsp;<code>grepl()</code>&nbsp;and friends.</p><p>Finally, sometimes exposing multiple strategies in one function isn&#8217;t the right move, and you&#8217;re better off creating more simpler functions. I think of this problem as &#8220;<a href="https://www.reddit.com/r/comics/comments/hzqw80/sheep_in_human_clothing/">three functions in a trench coat</a>&#8221; because it can feel like three or more functions crammed into one breaking apart at the seems.&nbsp;<code>forcats::fct_lump()</code>&nbsp;is a good example of this problem: it started off simple and then gained new strategies over time. Eventually it got so hard to explain that we decided to split apart in to three simpler functions. Another good example of this problem is the&nbsp;<code>rep()</code>&nbsp;function: I think it&#8217;s actually two functions in trench coat and it gets easier to understand if you pull them apart. See the&nbsp;<a href="https://design.tidyverse.org/cs-rep.html">rep() case study</a>&nbsp;for a full exploration including some of my thoughts about what you might name the functions and arguments.</p><p>Do these patterns resonate with you? Are there other functions in the tidyverse that you think do a particularly good or bad job of exposing a strategy? Please let me know in the comments!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://tidydesign.substack.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[My dad, Brian Wickham]]></title><description><![CDATA[My dad, Brian Walter Wickham, passed away peacefully last month.]]></description><link>https://tidydesign.substack.com/p/my-dad-brian-wickham</link><guid isPermaLink="false">https://tidydesign.substack.com/p/my-dad-brian-wickham</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 29 Sep 2023 13:03:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PWC6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>My dad, Brian Walter Wickham, passed away peacefully last month. We've known this was coming for a while, but it's still a big blow: he was a very important part of my life. You can get a sense of how he influenced the world through <a href="https://www.farmersjournal.ie/former-icbf-chief-executive-brian-wickham-passes-away-779918">this article in the Irish Farmer's Journal</a>, but here I wanted to reflect particularly on how he influenced my work.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PWC6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PWC6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 424w, https://substackcdn.com/image/fetch/$s_!PWC6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 848w, https://substackcdn.com/image/fetch/$s_!PWC6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!PWC6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PWC6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg" width="334" height="324.135989010989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1413,&quot;width&quot;:1456,&quot;resizeWidth&quot;:334,&quot;bytes&quot;:1210642,&quot;alt&quot;:&quot;A photo of Hadley and Brian Wickham on a beach in NZ. They're both wearing sun hats and are smiling.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A photo of Hadley and Brian Wickham on a beach in NZ. They're both wearing sun hats and are smiling." title="A photo of Hadley and Brian Wickham on a beach in NZ. They're both wearing sun hats and are smiling." srcset="https://substackcdn.com/image/fetch/$s_!PWC6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 424w, https://substackcdn.com/image/fetch/$s_!PWC6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 848w, https://substackcdn.com/image/fetch/$s_!PWC6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!PWC6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a840592-7525-485c-ae6e-3d00540e81df_2320x2252.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I was lucky to have access to computers from a very young age (I don't remember when exactly, but I think around 10), thanks to Dad having a laptop for work. This was in the era where laptops were extremely expensive, heavy, and barely transportable, but I still I have fond memories of using Lotus 1-2-3 (an early spreadsheeting tool), learning DOS, and playing Dune 2. In my early teens, I remember being greatly excited to find a documentation manual for DOS in a computer shop and convincing Dad to buy it for me.</p><p>Databases have been a big part of Dad's work through out his life, and he gave me "the talk" about <a href="https://en.wikipedia.org/wiki/Third_normal_form">Codd's third normal form</a> when I was around 15. This sparked my interest in MS Access, which lead to part time jobs creating and documenting databases in high school and university. One projects involved creating a database for the library at his work, and I still remember the feeling after I accidentally deleted a file that contained a week's worth of data entry. This lead to "the talk" about backups, the principles of which I have followed ever since. Dad's knowledge about and use of databases had a deep impact on my life, leading many years later to the idea of tidy data, a framing of Codd's rules that were easier to understand and apply to statistical data.</p><p>Much of Dad's work involved collecting data about cows (hence the databases), and one of his strong beliefs was that farmers should own their own data, and it should live together in a central database that could be used for the good of all. This made open source seem natural to me: why not build a community where developers collaboratively owned their code, and could work together to solve problems that were hard to tackle individually. Dad's belief in sharing his work for the betterment of all made adopting the principles of open source software seen obvious to me when I started producing software of my own.</p><p>Dad did his PhD at Cornell, so growing up it made doing a PhD overseas seem like a totally normal and reasonable to do. So when I got interested in statistics and computer science in my undergrad, applying to universities in the US seemed like the obvious choice. Unfortunately Dad had given me unrealistic sense of how long a PhD would take, since he finished his in only two years!</p><p>One of the things that most impressed me about Dad was his commitment to life long learning. He loved to embrace new technologies, and was a fluent user of FaceTime, AirBnB, and Uber (although he also loved to strike up conversations with strangers in a way that is very foreign to me). In his early 70s, he learned how to use GitHub and markdown so that he could edit "<a href="https://www.lulu.com/shop/alison-wickham/people-and-places-of-clonakilty/paperback/product-q9qk6e.html">People and Places of Clonakilty</a>", a book that my mum wrote and that Charlotte and I produced with Quarto. He&#8217;s a great role model as I get older; I want to continue to learn new things and embrace new technologies</p><p>Dad taught me how to chair a meeting, how to grill a steak, and how to change a tire. I admire his optimism, calm and thoughtful manner, and endless patience, and hope I can live up to his legacy. He will be greatly missed.</p>]]></content:encoded></item><item><title><![CDATA[Dot-dot-dot, bang-bang-bang, and do.call()]]></title><description><![CDATA[This week I wanted to talk through a bit of tidyverse design that I have mixed feelings about: !!!. What is !!! and when might you need it?]]></description><link>https://tidydesign.substack.com/p/dot-dot-dot-bang-bang-bang-and-docall</link><guid isPermaLink="false">https://tidydesign.substack.com/p/dot-dot-dot-bang-bang-bang-and-docall</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 11 Aug 2023 14:04:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zlfg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zlfg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zlfg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!zlfg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!zlfg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!zlfg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zlfg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png" width="380" height="380" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:380,&quot;bytes&quot;:1777666,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zlfg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!zlfg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!zlfg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!zlfg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4168365-aa82-4153-b5fa-64baf79b7a1c_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">DALL&#8226;E: painting of a robot shooting a water pistol at three tin cans. The tin cans are sitting on a wall. (I was trying for something that conveyed &#8220;bang, bang, bang&#8221; but this eventually I gave up)</figcaption></figure></div><p>This week I wanted to talk through a bit of tidyverse design that I have mixed feelings about:&nbsp;<code>!!!</code>. What is&nbsp;<code>!!!</code>&nbsp;and when might you need it? To understand it, you&#8217;ll need a little back story&#8230;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Tidy design principles! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Some functions want to work with both individual values and a list of values. For example, take&nbsp;<code>rbind()</code>. Sometimes you&#8217;ve created a couple of data frames by hand and want to join them together:</p><pre><code><code>df1 &lt;- data.frame(x = 1)
df2 &lt;- data.frame(x = 2)
both &lt;- rbind(df1, df2)</code></code></pre><p>But other times you have created an entire list of data frames, typically through the application of&nbsp;<code>lapply()</code>&nbsp;or friends:</p><pre><code><code>xs &lt;- 1:5
dfs &lt;- lapply(xs, \(x) data.frame(x = x))</code></code></pre><p>How can you join these into a single data frame? Just calling&nbsp;<code>rbind()</code>&nbsp;doesn&#8217;t do what you want:</p><pre><code><code>rbind(dfs)
#&gt;     [,1]         [,2]         [,3]         [,4]         [,5]        
#&gt; dfs data.frame,1 data.frame,1 data.frame,1 data.frame,1 data.frame,1</code></code></pre><p>And while you certainly&nbsp;<em>could</em>&nbsp;index them by hand, you lose much of the advantage of using&nbsp;<code>lapply()</code>&nbsp;in the first place:</p><pre><code><code>rbind(dfs[[1]], dfs[[2]], dfs[[3]], dfs[[4]], dfs[[5]])</code></code></pre><p>This problem is sometimes called splicing or splatting and occurs whenever you have a single object that contains elements that you want to be individual arguments.</p><p>The recommended solution for this problem in base R is to use&nbsp;<code>do.call()</code>.&nbsp;<code>do.call(rbind, dfs)</code>&nbsp;generates the call&nbsp;<code>rbind(dfs[[1]], rbind[[2]], &#8230;, rbind[[5]])</code>&nbsp;for you:</p><pre><code><code>do.call(rbind, dfs)
#&gt;   x
#&gt; 1 1
#&gt; 2 2
#&gt; 3 3
#&gt; 4 4
#&gt; 5 5</code></code></pre><p><code>do.call()</code>&nbsp;is an effective, if advanced, technique but it gets a little tricky if you want to supply additional arguments. For example, historically, it used to be important to set&nbsp;<code>stringsAsFactors = FALSE</code>&nbsp;which requires gymnastics like this:</p><pre><code><code>do.call(rbind, c(dfs, list(stringsAsFactors = FALSE)))</code></code></pre><p>This was one of the challenges I wanted to tackle in dplyr, so I came up with&nbsp;<code>bind_rows()</code>&nbsp;which tries to automatically figure out if you have a list of data frames or you&#8217;re supplying them individually:</p><pre><code><code>library(dplyr, warn.conflicts = FALSE)

bind_rows(df1, df2)
#&gt;   x
#&gt; 1 1
#&gt; 2 2

bind_rows(dfs)
#&gt;   x
#&gt; 1 1
#&gt; 2 2
#&gt; 3 3
#&gt; 4 4
#&gt; 5 5</code></code></pre><p>Unfortunately the heuristic we used to decide whether we were in the first case or the second case grew progressively more complicated over time, as people found problems or asked for new functionality. Now, while&nbsp;<code>bind_rows()</code>&nbsp;works correctly 99% of the time, it has some weird special cases, like below where the inputs can become columns, rather than rows.</p><pre><code><code>bind_rows(x = 1, y = 2)
#&gt; # A tibble: 1 &#215; 2
#&gt;       x     y
#&gt;   &lt;dbl&gt; &lt;dbl&gt;
#&gt; 1     1     2</code></code></pre><p>These problems soured us on the idea of &#8220;automatic&#8221; splicing so we started looking for other solutions:</p><ul><li><p>We could have a pair of functions, one that takes&nbsp;<code>&#8230;</code>&nbsp;and one that takes a list. This works for&nbsp;<code>bind_rows()</code>&nbsp;it turns out there are a lot of functions that take&nbsp;<code>&#8230;</code>&nbsp;where it would be nice to also take a list of objects so it lead to a substantial amount of duplication.</p></li><li><p>We could have a pair of arguments,&nbsp;<code>&#8230;</code>&nbsp;<code>.dots,</code>&nbsp;where you can supply individual arguments to&nbsp;<code>&#8230;</code>&nbsp;or a list of arguments to&nbsp;<code>.dots</code>. (I think I first recall seeing this approach in the RCurl package by Duncan Temple Lang.) But this would requires adding an additional argument to every function that uses&nbsp;<code>&#8230;</code>, and wouldn&#8217;t it be nice if we didn&#8217;t have to do that?</p></li></ul><p>Instead we found inspiration from tidy evaluation where we had recently solved a similar problem with&nbsp;<code>!!!</code>:</p><pre><code><code>library(rlang)

args &lt;- exprs(a, b, c + d)
expr(f(!!!args))
#&gt; f(a, b, c + d)</code></code></pre><p>So we introduced the idea of &#8220;dynamic dots&#8221;, an extension to&nbsp;<code>&#8230;</code>&nbsp;that incorporates some features we thought we useful from tidy evaluation: splicing with&nbsp;<code>!!!</code>&nbsp;and dynamic names with&nbsp;<code>:=</code>. Dynamic dots is implemented via&nbsp;<code>rlang::list2()</code>&nbsp;and it&#8217;s easy to use in your own functions if you find the idea appealing.</p><p>One place you can see this idea in use is&nbsp;<code>forcats::fct_cross()</code>, which creates a factor that contains all combinations of its inputs:</p><pre><code><code>library(forcats)

fruit &lt;- factor(c("apple", "kiwi", "apple", "apple"))
colour &lt;- factor(c("green", "green", "red", "green"))
fct_cross(fruit, colour)
#&gt; [1] apple:green kiwi:green  apple:red   apple:green
#&gt; Levels: apple:green kiwi:green apple:red</code></code></pre><p>Because&nbsp;<code>fct_cross()</code>&nbsp;uses dynamic dots (which you can find out by looking at the dots), if you happen to have a list of values, you can use&nbsp;<code>!!!</code>&nbsp;to splice them in:</p><pre><code><code>x &lt;- list(fruit = fruit, colour = colour)
fct_cross(!!!x)
#&gt; [1] apple:green kiwi:green  apple:red   apple:green
#&gt; Levels: apple:green kiwi:green apple:red</code></code></pre><p>(<code>fct_cross()</code>&nbsp;does a similar job to&nbsp;<code>interaction()</code>, which interestingly takes the automatic approach, so you can just call&nbsp;<code>interaction(x)</code>&nbsp;here. I don&#8217;t love the approach it takes because if&nbsp;<code>interaction(list(f1, f2))</code>&nbsp;works, you might expect&nbsp;<code>interaction(list(f1, f2), f3)</code>&nbsp;to work, but it does not, and it doesn&#8217;t give you a particularly useful error message).</p><p>We have yet to figure out a way to use dynamic dots in&nbsp;<code>dplyr::bind_rows()</code>&nbsp;without breaking existing usage, but it&#8217;s provided by the function that now does most of the work:&nbsp;<code>vctrs::vec_rbind()</code>. (vctrs is the package where we stick low-level operations on vectors and data frames that we use in multiple packages. It&#8217;s designed to be programmer-friendly rather than analyst-friendly so we don&#8217;t talk about it that much).</p><pre><code><code>vctrs::vec_rbind(!!!dfs)
#&gt;   x
#&gt; 1 1
#&gt; 2 2
#&gt; 3 3
#&gt; 4 4
#&gt; 5 5</code></code></pre><p>Because binding lists of data frames together is such a common operation we also provide&nbsp;<code>purrr::list_rbind()</code>&nbsp;and&nbsp;<code>purrr::list_cbind()</code>. If you look you&#8217;ll see their implementations are very simple!</p><p>Overall, I have mixed feelings about&nbsp;<code>!!!</code>. I love the elegance of it: it makes it easy to splices lists into&nbsp;<code>&#8230;</code>&nbsp;and it has a beautiful connection to tidy evaluation. But I worry it feels like magic to most R users and because it&#8217;s only supported by some functions, it&#8217;s not super clear how you know when you can use it, and you still also have to learn&nbsp;<code>do.call</code>&nbsp;or similar. And, at least for&nbsp;<code>bind_rows()</code>, we&#8217;ve still ended up with two functions!</p><p>All that said,&nbsp;<code>!!!</code>&nbsp;still feels like the &#8220;least worst&#8221; solution to me, although looking back I wonder if we might have been better off using the more explicit&nbsp;<code>.dots</code>&nbsp;argument. What do you think? Had you heard of&nbsp;<code>!!!</code>&nbsp;before reading this post? Have you ever used it to successfully solve a problem? Do you prefer&nbsp;<code>do.call()</code>&nbsp;or are their other approaches used by other packages that you think are better?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Tidy design principles! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Reducing clutter with an options object]]></title><description><![CDATA[New this week is a new chapter on reducing argument clutter by adding an options object. Sometimes you have a set of &#8220;second class&#8221; arguments that you don&#8217;t expect people to use very commonly, so you don&#8217;t want them cluttering up the function specification. If you want to give the user the ability to control them when needed, you can lump them all together into an &#8220;options&#8221; object.]]></description><link>https://tidydesign.substack.com/p/reducing-clutter-with-an-options</link><guid isPermaLink="false">https://tidydesign.substack.com/p/reducing-clutter-with-an-options</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 04 Aug 2023 16:03:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!UJTX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UJTX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UJTX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!UJTX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!UJTX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!UJTX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UJTX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png" width="324" height="324" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:324,&quot;bytes&quot;:1599038,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UJTX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!UJTX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!UJTX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!UJTX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73942709-1a43-4726-9022-22f38f116ac8_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">DALL&#8226;E: A painting of vacuum cleaner sucking up clutter from a room.</figcaption></figure></div><p>New this week is a new chapter on&nbsp;<a href="https://design.tidyverse.org/argument-clutter.html">reducing argument clutter by adding an options object</a>. Sometimes you have a set of &#8220;second class&#8221; arguments that you don&#8217;t expect people to use very commonly, so you don&#8217;t want them cluttering up the function specification. If you want to give the user the ability to control them when needed, you can lump them all together into an &#8220;options&#8221; object.</p><p>These are used in base R modelling functions (e.g.&nbsp;<code>glm()</code>,&nbsp;<code>loess()</code>) to control the details of the underlying numerical algorithm. For example, take this model from the glm docs:</p><pre><code><code>data(anorexia, package = "MASS")

mod &lt;- glm(
  Postwt ~ Prewt + Treat + offset(Prewt),
  family = gaussian,
  data = anorexia
)</code></code></pre><p>If you want to understand how model convergence is going you can set the&nbsp;<code>trace = TRUE</code>&nbsp;in&nbsp;<code>glm.control()</code>:</p><pre><code><code>mod &lt;- glm(
  Postwt ~ Prewt + Treat + offset(Prewt),
  family = gaussian,
  data = anorexia,
  control = glm.control(trace = TRUE)
)
#&gt; Deviance = 3311.263 Iterations - 1
#&gt; Deviance = 3311.263 Iterations - 2
#&gt; Deviance = 4525.386 Iterations - 1
#&gt; Deviance = 4525.386 Iterations - 2</code></code></pre><p>99% of the time you don&#8217;t need to know these arguments exist, but they are available if you ever need to debug a convergence failure.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://tidydesign.substack.com/subscribe?"><span>Subscribe now</span></a></p><p>You can see the same pattern in&nbsp;<code>readr::locale()</code>&nbsp;and&nbsp;<code>readr::date_names()</code>. When parsing dates, you often need to know the names of the month, and that obviously varies by location.&nbsp;<code>locale()</code>&nbsp;allows you to set&nbsp;<code>date_names</code>&nbsp;to a two-letter country code to use common locations that baked in readr, but what happens if you want to parse dates from an unsupported language?</p><p>For example, take Austrian which came up in a&nbsp;<a href="https://github.com/tidyverse/readr/issues/1467">recent readr issue</a>. Austrian month names are mostly the same as German but use J&#228;nner instead of Januar and Feber instead of Februar. We can parse Austrian date times by first taking the German date names structure and modifying it:</p><pre><code><code>library(readr)

au &lt;- readr::date_names_lang("de")
au$mon[1:2] &lt;- c("J&#228;nner", "Feber")
au
#&gt; &lt;date_names&gt;
#&gt; Days:   Sonntag (So.), Montag (Mo.), Dienstag (Di.), Mittwoch (Mi.), Donnerstag
#&gt;         (Do.), Freitag (Fr.), Samstag (Sa.)
#&gt; Months: J&#228;nner (Jan.), Feber (Feb.), M&#228;rz (M&#228;rz), April (Apr.), Mai (Mai), Juni
#&gt;         (Juni), Juli (Juli), August (Aug.), September (Sep.), Oktober
#&gt;         (Okt.), November (Nov.), Dezember (Dez.)
#&gt; AM/PM:  vorm./nachm.</code></code></pre><p>Now we can pass this to object to&nbsp;<code>locale()</code>, and the locale object to a parsing function:</p><pre><code><code>parse_date("15. J&#228;nner 2015", "%d. %B %Y", locale = locale(date_names = au))
#&gt; [1] "2015-01-15"</code></code></pre><p>I like how this hierarchy of option arguments buries something that you rarely need but still makes it accessible.</p><p>Where else have you seen this pattern? Have you written functions where it would be useful? Are there places in the tidyverse that you think should use this pattern but don&#8217;t? Please let me know in the comments!</p><div><hr></div><p>Thanks to everyone who contributed in the comments last week! The results aren&#8217;t ready to read yet, but have really helped my thinking for two new chapters &#8220;make strategies explicit&#8221; and &#8220;argument meaning should be independent&#8221;.</p>]]></content:encoded></item><item><title><![CDATA[About Tidy design principles]]></title><description><![CDATA[And one example of a principle: function definitions should be scannable.]]></description><link>https://tidydesign.substack.com/p/about-tidy-design-principles</link><guid isPermaLink="false">https://tidydesign.substack.com/p/about-tidy-design-principles</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 28 Jul 2023 13:56:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bSJA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bSJA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bSJA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!bSJA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!bSJA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!bSJA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bSJA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png" width="342" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:342,&quot;bytes&quot;:1553970,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bSJA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!bSJA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!bSJA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!bSJA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bfce23c-23e4-4e24-98d7-fda1c3c8d644_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">DALL&#8226;E: a painting of a robot with a magnifying glass</figcaption></figure></div><p>The goal of this newsletter is to get feedback as I work on a new book called "Tidy design principles". But what's the point of that book? R has a very rich literature on statistics and data science, there are relatively few books that focus on programming. I've written a couple (<a href="https://adv-r.hadley.nz/">Advanced R</a>&nbsp;and&nbsp;<a href="https://r-pkgs.org/">R Packages</a>) but neither really talks about how to write good code R code (or even discusses what good code means).</p><p>That's what I want to focus on in "<a href="https://design.tidyverse.org/">Tidy design principles</a>": how do you write high-quality R code that's easy to understand, unlikely to fail in unexpected ways, and flexible enough to grow with your needs. This book will be organised around the idea of "design patterns". This was an idea that I encountered early in my programming journey and I found it very impactful.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading tidy design principles! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>The idea of a design pattern is to come up with a catchy name that maps a common programming challenge to an effective solution. The catchy name is important because it serves as a handle for your memory and a convenient shorthand when discussing code with others. I first heard about design patterns when I was a CS undergrad learning Java (a much less flexible language than R) and I read the popular read&nbsp;<a href="https://en.wikipedia.org/wiki/Design_Patterns">Design Patterns: Elements of Reusable Object-Oriented Software</a>&nbsp;book.</p><p>I later learned that the idea of design patterns originated not from computer science, but from architecture, particularly&nbsp;<a href="https://en.wikipedia.org/wiki/A_Pattern_Language">A Pattern Language: Towns, Buildings, Construction</a>&nbsp;by Christopher Alexander. This book resonated with me even more strongly than the CS patterns, and if you have any interest in architecture, I highly recommend reading it. I particularly liked that the patterns spanned many levels of detail, all the way from organising entire communities to how you might select chairs for a single room.</p><p>That's the spirit in which I write "Tidy design principles". I want to name common problem solving patterns in R, and write them up so that others can easily use them. That means that this book will have rather a different structure to my previous books. It will have a large number of relatively short chapters, each of which describes a pattern: what it is, why it's important, where you can see it in the wild, and how you can apply it to your code. You might skim the whole book once, but you'll generally use it by referring to the patterns that apply specifically to your current problem.</p><p>I also want to include some bigger principles that help you weigh conflicting patterns as well as case studies that illuminate some of our thinking when we've designed various parts of tidyverse (particularly parts that we now regret).</p><div><hr></div><p>This week I've identified one bigger principle: the definition of a function should be scannable, giving you useful information at a glance. This principle is important because you see the function definition in lots of places, like in autocomplete and at the top of the documentation. It's super useful if that glance can give you useful insight into the function.</p><p>So far, I've gathered six patterns related to this principle:</p><ul><li><p>You should be able to tell what affects the output of the function because&nbsp;<a href="https://design.tidyverse.org/inputs-explicit.html">all inputs are explicit arguments</a>.</p></li><li><p>You know which arguments are most important because&nbsp;<a href="https://design.tidyverse.org/important-args-first.html">they come first</a>.</p></li><li><p>You can tell if an argument is required or optional based on the&nbsp;<a href="https://design.tidyverse.org/required-no-defaults.html">the presence or absence of a default</a>&nbsp;and whether it&nbsp;<a href="https://design.tidyverse.org/dots-after-required.html">comes before or after&nbsp;</a><code>&#8230;</code>.</p></li><li><p>You can easily figure out the defaults because&nbsp;<a href="https://design.tidyverse.org/defaults-short-and-sweet.html">they are short and sweet</a>.</p></li><li><p>And if an argument has a small set of valid inputs, they are&nbsp;<a href="https://design.tidyverse.org/enumerate-options.html">explicitly enumerated in the default</a>.</p></li></ul><p>What do you think of this principle and the various patterns that make it up? Does it resonate with you? Do you think I&#8217;ve missed something important?</p><p>One other pattern I've been noodling on is the idea that one argument shouldn't affect the meaning of another argument. This seems like an important principle, but so far I've only been able to come up with one example:&nbsp;<code>library()</code>, where the&nbsp;<code>character.only</code>&nbsp;argument affects the meaning of the&nbsp;<code>package</code>&nbsp;argument:</p><pre><code><code>ggplot2 &lt;- "dplyr"

# Loads ggplot2
library(ggplot2)

# Loads dplyr
library(ggplot2, character.only = TRUE)</code></code></pre><p>Given that I only have one example, it doesn't seem worthwhile to write it up as a pattern, but maybe you've encountered other examples of this problem. If so, please let me know in the comments!</p>]]></content:encoded></item><item><title><![CDATA[Argument ordering]]></title><description><![CDATA[Bringing tidy design principles back to life and thinking about argument order]]></description><link>https://tidydesign.substack.com/p/argument-ordering</link><guid isPermaLink="false">https://tidydesign.substack.com/p/argument-ordering</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 21 Jul 2023 15:58:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rLID!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rLID!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rLID!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!rLID!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!rLID!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!rLID!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rLID!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png" width="342" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:342,&quot;bytes&quot;:1633122,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rLID!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!rLID!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!rLID!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!rLID!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8c330a08-7e75-42f6-be34-f9227f7693cb_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">DALL&#8226;E: a painting of a robot moving cans</figcaption></figure></div><p>This week I&#8217;ve focussed on bringing <a href="https://design.tidyverse.org">the tidy design principles website</a> back to life. Jenny Bryan and I started on this book all the way back in 2018, and there have obviously been some quite a few technology changes in the meantime. (Un)fortunately I have quite a bit of experience converting bookdown books to <a href="https://quarto.org">Quarto</a> and tools like <code>knitr::convert_chunk_header()</code> do much of the heavy lifting, so overall it was just a couple of hours of work. But it&#8217;s highly likely I missed some stuff in the translation, so please let me know if you see anything that looks odd.</p><p>I also managed to get some thoughts in place about how you might think about ordering function arguments. I think both patterns are fairly obvious (please let me know if you think otherwise!) but worth writing down.</p><p>The first principle is that you should put the <a href="https://design.tidyverse.org/args-data-details.html">most important arguments first</a>. Of course, this raises the question of what makes an argument important? It&#8217;s hard for me to make this concrete (so I hope you&#8217;ll send me your thoughts too), but there are three clear cases:</p><ul><li><p>If a function transforms some existing object, that object is most important and should be the first argument.</p></li><li><p>Other arguments that affect the &#8220;shape&#8221; of the output (either the size or the type) are usually very important and should be near the front.</p></li><li><p>Optional arguments are clearly least important and should be at the end.</p></li></ul><p>Do you think there are tidyverse functions that break this rule? Please let me know so I can include them as examples!</p><p>The second principle is that if you use &#8230;, it should be <a href="https://design.tidyverse.org/dots-position.html">placed between the required and optional arguments</a>. This is important because of the way that argument matching works in R: you can supply arguments by position (like mean(x)), by full name (like mean(na.rm = TRUE)), or by part of the name (like mean(na = TRUE)). Placing arguments after &#8230; means that you can only supply them by their full name; not only does this force users into a clearer style, it also makes it easier for you to change the order of arguments in the future (e.g. to move important arguments earlier, as above).</p><p>The goal of writing this newsletter is to get your thoughts. So please let me know if you think these patterns are useful or wrong or anything in between!</p><p>&#8212; Hadley</p>]]></content:encoded></item><item><title><![CDATA[Coming soon]]></title><description><![CDATA[This is my substack where I plan to regular post about progress on my current book project, tidy design principles. The goal of this book is to write up what the tidyverse team and I know about designing and implementing quality R code. It&#8217;s roughly aimed at someone who&#8217;s comfortable doing data science in R (e.g. someone who&#8217;s read]]></description><link>https://tidydesign.substack.com/p/coming-soon</link><guid isPermaLink="false">https://tidydesign.substack.com/p/coming-soon</guid><dc:creator><![CDATA[Hadley Wickham]]></dc:creator><pubDate>Fri, 21 Jul 2023 14:09:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!d3td!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d3td!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d3td!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!d3td!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!d3td!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!d3td!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d3td!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png" width="316" height="316" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:316,&quot;bytes&quot;:1584986,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!d3td!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 424w, https://substackcdn.com/image/fetch/$s_!d3td!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 848w, https://substackcdn.com/image/fetch/$s_!d3td!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!d3td!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95245820-8aa0-4668-8543-b376e5143aef_1024x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">DALL&#8226;E: a painting of a robot looking into the future</figcaption></figure></div><p>This is my substack where I plan to regular post about progress on my current book project, <a href="https://design.tidyverse.org">tidy design principles</a>. The goal of this book is to write up what the tidyverse team and I know about designing and implementing quality R code. It&#8217;s roughly aimed at someone who&#8217;s comfortable doing data science in R (e.g. someone who&#8217;s read <a href="https://r4ds.hadley.nz">R for Data Science</a>) has been writing R functions (or even <a href="https://r-pkgs.org">packages</a>) for a while, and now wants to understand what makes for a &#8220;good&#8221; function (and maybe wonders what that even means). I also hope it will serve as a useful resource when <a href="https://code-review.tidyverse.org">reviewing code</a> since you&#8217;ll be able to link to a chapter that discusses a potential pain point without having to write it up yourself.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://tidydesign.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://tidydesign.substack.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item></channel></rss>