1797 lines
148 KiB
HTML
1797 lines
148 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html>
|
||
|
||
<head>
|
||
|
||
<meta charset="utf-8" />
|
||
<meta name="generator" content="pandoc" />
|
||
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
||
|
||
|
||
<title>S3 vectors</title>
|
||
|
||
<script>// Pandoc 2.9 adds attributes on both header and div. We remove the former (to
|
||
// be compatible with the behavior of Pandoc < 2.8).
|
||
document.addEventListener('DOMContentLoaded', function(e) {
|
||
var hs = document.querySelectorAll("div.section[class*='level'] > :first-child");
|
||
var i, h, a;
|
||
for (i = 0; i < hs.length; i++) {
|
||
h = hs[i];
|
||
if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6
|
||
a = h.attributes;
|
||
while (a.length > 0) h.removeAttribute(a[0].name);
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style type="text/css">
|
||
code{white-space: pre-wrap;}
|
||
span.smallcaps{font-variant: small-caps;}
|
||
span.underline{text-decoration: underline;}
|
||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||
ul.task-list{list-style: none;}
|
||
</style>
|
||
|
||
|
||
|
||
<style type="text/css">
|
||
code {
|
||
white-space: pre;
|
||
}
|
||
.sourceCode {
|
||
overflow: visible;
|
||
}
|
||
</style>
|
||
<style type="text/css" data-origin="pandoc">
|
||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||
pre > code.sourceCode > span { line-height: 1.25; }
|
||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||
.sourceCode { overflow: visible; }
|
||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||
div.sourceCode { margin: 1em 0; }
|
||
pre.sourceCode { margin: 0; }
|
||
@media screen {
|
||
div.sourceCode { overflow: auto; }
|
||
}
|
||
@media print {
|
||
pre > code.sourceCode { white-space: pre-wrap; }
|
||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||
}
|
||
pre.numberSource code
|
||
{ counter-reset: source-line 0; }
|
||
pre.numberSource code > span
|
||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||
pre.numberSource code > span > a:first-child::before
|
||
{ content: counter(source-line);
|
||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||
border: none; display: inline-block;
|
||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||
-khtml-user-select: none; -moz-user-select: none;
|
||
-ms-user-select: none; user-select: none;
|
||
padding: 0 4px; width: 4em;
|
||
color: #aaaaaa;
|
||
}
|
||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||
div.sourceCode
|
||
{ }
|
||
@media screen {
|
||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||
}
|
||
code span.al { color: #ff0000; font-weight: bold; }
|
||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; }
|
||
code span.at { color: #7d9029; }
|
||
code span.bn { color: #40a070; }
|
||
code span.bu { color: #008000; }
|
||
code span.cf { color: #007020; font-weight: bold; }
|
||
code span.ch { color: #4070a0; }
|
||
code span.cn { color: #880000; }
|
||
code span.co { color: #60a0b0; font-style: italic; }
|
||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; }
|
||
code span.do { color: #ba2121; font-style: italic; }
|
||
code span.dt { color: #902000; }
|
||
code span.dv { color: #40a070; }
|
||
code span.er { color: #ff0000; font-weight: bold; }
|
||
code span.ex { }
|
||
code span.fl { color: #40a070; }
|
||
code span.fu { color: #06287e; }
|
||
code span.im { color: #008000; font-weight: bold; }
|
||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; }
|
||
code span.kw { color: #007020; font-weight: bold; }
|
||
code span.op { color: #666666; }
|
||
code span.ot { color: #007020; }
|
||
code span.pp { color: #bc7a00; }
|
||
code span.sc { color: #4070a0; }
|
||
code span.ss { color: #bb6688; }
|
||
code span.st { color: #4070a0; }
|
||
code span.va { color: #19177c; }
|
||
code span.vs { color: #4070a0; }
|
||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; }
|
||
</style>
|
||
<script>
|
||
// apply pandoc div.sourceCode style to pre.sourceCode instead
|
||
(function() {
|
||
var sheets = document.styleSheets;
|
||
for (var i = 0; i < sheets.length; i++) {
|
||
if (sheets[i].ownerNode.dataset["origin"] !== "pandoc") continue;
|
||
try { var rules = sheets[i].cssRules; } catch (e) { continue; }
|
||
var j = 0;
|
||
while (j < rules.length) {
|
||
var rule = rules[j];
|
||
// check if there is a div.sourceCode rule
|
||
if (rule.type !== rule.STYLE_RULE || rule.selectorText !== "div.sourceCode") {
|
||
j++;
|
||
continue;
|
||
}
|
||
var style = rule.style.cssText;
|
||
// check if color or background-color is set
|
||
if (rule.style.color === '' && rule.style.backgroundColor === '') {
|
||
j++;
|
||
continue;
|
||
}
|
||
// replace div.sourceCode by a pre.sourceCode rule
|
||
sheets[i].deleteRule(j);
|
||
sheets[i].insertRule('pre.sourceCode{' + style + '}', j);
|
||
}
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
|
||
|
||
|
||
<style type="text/css">body {
|
||
background-color: #fff;
|
||
margin: 1em auto;
|
||
max-width: 700px;
|
||
overflow: visible;
|
||
padding-left: 2em;
|
||
padding-right: 2em;
|
||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||
font-size: 14px;
|
||
line-height: 1.35;
|
||
}
|
||
#TOC {
|
||
clear: both;
|
||
margin: 0 0 10px 10px;
|
||
padding: 4px;
|
||
width: 400px;
|
||
border: 1px solid #CCCCCC;
|
||
border-radius: 5px;
|
||
background-color: #f6f6f6;
|
||
font-size: 13px;
|
||
line-height: 1.3;
|
||
}
|
||
#TOC .toctitle {
|
||
font-weight: bold;
|
||
font-size: 15px;
|
||
margin-left: 5px;
|
||
}
|
||
#TOC ul {
|
||
padding-left: 40px;
|
||
margin-left: -1.5em;
|
||
margin-top: 5px;
|
||
margin-bottom: 5px;
|
||
}
|
||
#TOC ul ul {
|
||
margin-left: -2em;
|
||
}
|
||
#TOC li {
|
||
line-height: 16px;
|
||
}
|
||
table {
|
||
margin: 1em auto;
|
||
border-width: 1px;
|
||
border-color: #DDDDDD;
|
||
border-style: outset;
|
||
border-collapse: collapse;
|
||
}
|
||
table th {
|
||
border-width: 2px;
|
||
padding: 5px;
|
||
border-style: inset;
|
||
}
|
||
table td {
|
||
border-width: 1px;
|
||
border-style: inset;
|
||
line-height: 18px;
|
||
padding: 5px 5px;
|
||
}
|
||
table, table th, table td {
|
||
border-left-style: none;
|
||
border-right-style: none;
|
||
}
|
||
table thead, table tr.even {
|
||
background-color: #f7f7f7;
|
||
}
|
||
p {
|
||
margin: 0.5em 0;
|
||
}
|
||
blockquote {
|
||
background-color: #f6f6f6;
|
||
padding: 0.25em 0.75em;
|
||
}
|
||
hr {
|
||
border-style: solid;
|
||
border: none;
|
||
border-top: 1px solid #777;
|
||
margin: 28px 0;
|
||
}
|
||
dl {
|
||
margin-left: 0;
|
||
}
|
||
dl dd {
|
||
margin-bottom: 13px;
|
||
margin-left: 13px;
|
||
}
|
||
dl dt {
|
||
font-weight: bold;
|
||
}
|
||
ul {
|
||
margin-top: 0;
|
||
}
|
||
ul li {
|
||
list-style: circle outside;
|
||
}
|
||
ul ul {
|
||
margin-bottom: 0;
|
||
}
|
||
pre, code {
|
||
background-color: #f7f7f7;
|
||
border-radius: 3px;
|
||
color: #333;
|
||
white-space: pre-wrap;
|
||
}
|
||
pre {
|
||
border-radius: 3px;
|
||
margin: 5px 0px 10px 0px;
|
||
padding: 10px;
|
||
}
|
||
pre:not([class]) {
|
||
background-color: #f7f7f7;
|
||
}
|
||
code {
|
||
font-family: Consolas, Monaco, 'Courier New', monospace;
|
||
font-size: 85%;
|
||
}
|
||
p > code, li > code {
|
||
padding: 2px 0px;
|
||
}
|
||
div.figure {
|
||
text-align: center;
|
||
}
|
||
img {
|
||
background-color: #FFFFFF;
|
||
padding: 2px;
|
||
border: 1px solid #DDDDDD;
|
||
border-radius: 3px;
|
||
border: 1px solid #CCCCCC;
|
||
margin: 0 5px;
|
||
}
|
||
h1 {
|
||
margin-top: 0;
|
||
font-size: 35px;
|
||
line-height: 40px;
|
||
}
|
||
h2 {
|
||
border-bottom: 4px solid #f7f7f7;
|
||
padding-top: 10px;
|
||
padding-bottom: 2px;
|
||
font-size: 145%;
|
||
}
|
||
h3 {
|
||
border-bottom: 2px solid #f7f7f7;
|
||
padding-top: 10px;
|
||
font-size: 120%;
|
||
}
|
||
h4 {
|
||
border-bottom: 1px solid #f7f7f7;
|
||
margin-left: 8px;
|
||
font-size: 105%;
|
||
}
|
||
h5, h6 {
|
||
border-bottom: 1px solid #ccc;
|
||
font-size: 105%;
|
||
}
|
||
a {
|
||
color: #0033dd;
|
||
text-decoration: none;
|
||
}
|
||
a:hover {
|
||
color: #6666ff; }
|
||
a:visited {
|
||
color: #800080; }
|
||
a:visited:hover {
|
||
color: #BB00BB; }
|
||
a[href^="http:"] {
|
||
text-decoration: underline; }
|
||
a[href^="https:"] {
|
||
text-decoration: underline; }
|
||
|
||
code > span.kw { color: #555; font-weight: bold; }
|
||
code > span.dt { color: #902000; }
|
||
code > span.dv { color: #40a070; }
|
||
code > span.bn { color: #d14; }
|
||
code > span.fl { color: #d14; }
|
||
code > span.ch { color: #d14; }
|
||
code > span.st { color: #d14; }
|
||
code > span.co { color: #888888; font-style: italic; }
|
||
code > span.ot { color: #007020; }
|
||
code > span.al { color: #ff0000; font-weight: bold; }
|
||
code > span.fu { color: #900; font-weight: bold; }
|
||
code > span.er { color: #a61717; background-color: #e3d2d2; }
|
||
</style>
|
||
|
||
|
||
|
||
|
||
</head>
|
||
|
||
<body>
|
||
|
||
|
||
|
||
|
||
<h1 class="title toc-ignore">S3 vectors</h1>
|
||
|
||
|
||
|
||
<p>This vignette shows you how to create your own S3 vector classes. It
|
||
focuses on the aspects of making a vector class that every class needs
|
||
to worry about; you’ll also need to provide methods that actually make
|
||
the vector useful.</p>
|
||
<p>I assume that you’re already familiar with the basic machinery of S3,
|
||
and the vocabulary I use in Advanced R: constructor, helper, and
|
||
validator. If not, I recommend reading at least the first two sections
|
||
of <a href="https://adv-r.hadley.nz/s3.html">the S3 chapter</a> of
|
||
<em>Advanced R</em>.</p>
|
||
<p>This article refers to “vectors of numbers” as <em>double
|
||
vectors</em>. Here, “double” stands for <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">“double
|
||
precision floating point number”</a>, see also
|
||
<code>double()</code>.</p>
|
||
<div class="sourceCode" id="cb1"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" tabindex="-1"></a><span class="fu">library</span>(vctrs)</span>
|
||
<span id="cb1-2"><a href="#cb1-2" tabindex="-1"></a><span class="fu">library</span>(rlang)</span>
|
||
<span id="cb1-3"><a href="#cb1-3" tabindex="-1"></a><span class="fu">library</span>(zeallot)</span></code></pre></div>
|
||
<p>This vignette works through five big topics:</p>
|
||
<ul>
|
||
<li>The basics of creating a new vector class with vctrs.</li>
|
||
<li>The coercion and casting system.</li>
|
||
<li>The record and list-of types.</li>
|
||
<li>Equality and comparison proxies.</li>
|
||
<li>Arithmetic operators.</li>
|
||
</ul>
|
||
<p>They’re collectively demonstrated with a number of simple S3
|
||
classes:</p>
|
||
<ul>
|
||
<li><p>Percent: a double vector that prints as a percentage. This
|
||
illustrates the basic mechanics of class creation, coercion, and
|
||
casting.</p></li>
|
||
<li><p>Decimal: a double vector that always prints with a fixed number
|
||
of decimal places. This class has an attribute which needs a little
|
||
extra care in casts and coercions.</p></li>
|
||
<li><p>Cached sum: a double vector that caches the total sum in an
|
||
attribute. The attribute depends on the data, so needs extra
|
||
care.</p></li>
|
||
<li><p>Rational: a pair of integer vectors that defines a rational
|
||
number like <code>2 / 3</code>. This introduces you to the record style,
|
||
and to the equality and comparison operators. It also needs special
|
||
handling for <code>+</code>, <code>-</code>, and friends.</p></li>
|
||
<li><p>Polynomial: a list of integer vectors that define polynomials
|
||
like <code>1 + x - x^3</code>. Sorting such vectors correctly requires a
|
||
custom equality method.</p></li>
|
||
<li><p>Meter: a numeric vector with meter units. This is the simplest
|
||
possible class with interesting algebraic properties.</p></li>
|
||
<li><p>Period and frequency: a pair of classes represent a period, or
|
||
its inverse, frequency. This allows us to explore more arithmetic
|
||
operators.</p></li>
|
||
</ul>
|
||
<div id="basics" class="section level2">
|
||
<h2>Basics</h2>
|
||
<p>In this section you’ll learn how to create a new vctrs class by
|
||
calling <code>new_vctr()</code>. This creates an object with class
|
||
<code>vctrs_vctr</code> which has a number of methods. These are
|
||
designed to make your life as easy as possible. For example:</p>
|
||
<ul>
|
||
<li><p>The <code>print()</code> and <code>str()</code> methods are
|
||
defined in terms of <code>format()</code> so you get a pleasant,
|
||
consistent display as soon as you’ve made your <code>format()</code>
|
||
method.</p></li>
|
||
<li><p>You can immediately put your new vector class in a data frame
|
||
because <code>as.data.frame.vctrs_vctr()</code> does the right
|
||
thing.</p></li>
|
||
<li><p>Subsetting (<code>[</code>, <code>[[</code>, and <code>$</code>),
|
||
<code>length<-</code>, and <code>rep()</code> methods automatically
|
||
preserve attributes because they use <code>vec_restore()</code>. A
|
||
default <code>vec_restore()</code> works for all classes where the
|
||
attributes are data-independent, and can easily be customised when the
|
||
attributes do depend on the data.</p></li>
|
||
<li><p>Default subset-assignment methods (<code>[<-</code>,
|
||
<code>[[<-</code>, and <code>$<-</code>) follow the principle that
|
||
the new values should be coerced to match the existing vector. This
|
||
gives predictable behaviour and clear error messages.</p></li>
|
||
</ul>
|
||
<div id="percent-class" class="section level3">
|
||
<h3>Percent class</h3>
|
||
<p>In this section, I’ll show you how to make a <code>percent</code>
|
||
class, i.e., a double vector that is printed as a percentage. We start
|
||
by defining a low-level <a href="https://adv-r.hadley.nz/s3.html#s3-constrcutor">constructor</a> to
|
||
check types and/or sizes and call <code>new_vctr()</code>.</p>
|
||
<p><code>percent</code> is built on a double vector of any length and
|
||
doesn’t have any attributes.</p>
|
||
<div class="sourceCode" id="cb2"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb2-1"><a href="#cb2-1" tabindex="-1"></a>new_percent <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>()) {</span>
|
||
<span id="cb2-2"><a href="#cb2-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_double</span>(x)) {</span>
|
||
<span id="cb2-3"><a href="#cb2-3" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`x` must be a double vector."</span>)</span>
|
||
<span id="cb2-4"><a href="#cb2-4" tabindex="-1"></a> }</span>
|
||
<span id="cb2-5"><a href="#cb2-5" tabindex="-1"></a> <span class="fu">new_vctr</span>(x, <span class="at">class =</span> <span class="st">"vctrs_percent"</span>)</span>
|
||
<span id="cb2-6"><a href="#cb2-6" tabindex="-1"></a>}</span>
|
||
<span id="cb2-7"><a href="#cb2-7" tabindex="-1"></a></span>
|
||
<span id="cb2-8"><a href="#cb2-8" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">new_percent</span>(<span class="fu">c</span>(<span class="fu">seq</span>(<span class="dv">0</span>, <span class="dv">1</span>, <span class="at">length.out =</span> <span class="dv">4</span>), <span class="cn">NA</span>))</span>
|
||
<span id="cb2-9"><a href="#cb2-9" tabindex="-1"></a>x</span>
|
||
<span id="cb2-10"><a href="#cb2-10" tabindex="-1"></a><span class="co">#> <vctrs_percent[5]></span></span>
|
||
<span id="cb2-11"><a href="#cb2-11" tabindex="-1"></a><span class="co">#> [1] 0.0000000 0.3333333 0.6666667 1.0000000 NA</span></span>
|
||
<span id="cb2-12"><a href="#cb2-12" tabindex="-1"></a></span>
|
||
<span id="cb2-13"><a href="#cb2-13" tabindex="-1"></a><span class="fu">str</span>(x)</span>
|
||
<span id="cb2-14"><a href="#cb2-14" tabindex="-1"></a><span class="co">#> vctrs_pr [1:5] 0.0000000, 0.3333333, 0.6666667, 1.0000000, NA</span></span></code></pre></div>
|
||
<p>Note that we prefix the name of the class with the name of the
|
||
package. This prevents conflicting definitions between packages. For
|
||
packages that implement only one class (such as <a href="https://blob.tidyverse.org/">blob</a>), it’s fine to use the
|
||
package name without prefix as the class name.</p>
|
||
<p>We then follow up with a user friendly <a href="https://adv-r.hadley.nz/s3.html#helpers">helper</a>. Here we’ll
|
||
use <code>vec_cast()</code> to allow it to accept anything coercible to
|
||
a double:</p>
|
||
<div class="sourceCode" id="cb3"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb3-1"><a href="#cb3-1" tabindex="-1"></a>percent <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>()) {</span>
|
||
<span id="cb3-2"><a href="#cb3-2" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast</span>(x, <span class="fu">double</span>())</span>
|
||
<span id="cb3-3"><a href="#cb3-3" tabindex="-1"></a> <span class="fu">new_percent</span>(x)</span>
|
||
<span id="cb3-4"><a href="#cb3-4" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Before you go on, check that user-friendly constructor returns a
|
||
zero-length vector when called with no arguments. This makes it easy to
|
||
use as a prototype.</p>
|
||
<div class="sourceCode" id="cb4"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" tabindex="-1"></a><span class="fu">new_percent</span>()</span>
|
||
<span id="cb4-2"><a href="#cb4-2" tabindex="-1"></a><span class="co">#> <vctrs_percent[0]></span></span>
|
||
<span id="cb4-3"><a href="#cb4-3" tabindex="-1"></a><span class="fu">percent</span>()</span>
|
||
<span id="cb4-4"><a href="#cb4-4" tabindex="-1"></a><span class="co">#> <vctrs_percent[0]></span></span></code></pre></div>
|
||
<p>For the convenience of your users, consider implementing an
|
||
<code>is_percent()</code> function:</p>
|
||
<div class="sourceCode" id="cb5"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb5-1"><a href="#cb5-1" tabindex="-1"></a>is_percent <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb5-2"><a href="#cb5-2" tabindex="-1"></a> <span class="fu">inherits</span>(x, <span class="st">"vctrs_percent"</span>)</span>
|
||
<span id="cb5-3"><a href="#cb5-3" tabindex="-1"></a>}</span></code></pre></div>
|
||
</div>
|
||
<div id="format-method" class="section level3">
|
||
<h3><code>format()</code> method</h3>
|
||
<p>The first method for every class should almost always be a
|
||
<code>format()</code> method. This should return a character vector the
|
||
same length as <code>x</code>. The easiest way to do this is to rely on
|
||
one of R’s low-level formatting functions like
|
||
<code>formatC()</code>:</p>
|
||
<div class="sourceCode" id="cb6"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb6-1"><a href="#cb6-1" tabindex="-1"></a>format.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb6-2"><a href="#cb6-2" tabindex="-1"></a> out <span class="ot"><-</span> <span class="fu">formatC</span>(<span class="fu">signif</span>(<span class="fu">vec_data</span>(x) <span class="sc">*</span> <span class="dv">100</span>, <span class="dv">3</span>))</span>
|
||
<span id="cb6-3"><a href="#cb6-3" tabindex="-1"></a> out[<span class="fu">is.na</span>(x)] <span class="ot"><-</span> <span class="cn">NA</span></span>
|
||
<span id="cb6-4"><a href="#cb6-4" tabindex="-1"></a> out[<span class="sc">!</span><span class="fu">is.na</span>(x)] <span class="ot"><-</span> <span class="fu">paste0</span>(out[<span class="sc">!</span><span class="fu">is.na</span>(x)], <span class="st">"%"</span>)</span>
|
||
<span id="cb6-5"><a href="#cb6-5" tabindex="-1"></a> out</span>
|
||
<span id="cb6-6"><a href="#cb6-6" tabindex="-1"></a>}</span></code></pre></div>
|
||
<div class="sourceCode" id="cb7"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb7-1"><a href="#cb7-1" tabindex="-1"></a>x</span>
|
||
<span id="cb7-2"><a href="#cb7-2" tabindex="-1"></a><span class="co">#> <vctrs_percent[5]></span></span>
|
||
<span id="cb7-3"><a href="#cb7-3" tabindex="-1"></a><span class="co">#> [1] 0% 33.3% 66.7% 100% <NA></span></span></code></pre></div>
|
||
<p>(Note the use of <code>vec_data()</code> so <code>format()</code>
|
||
doesn’t get stuck in an infinite loop, and that I take a little care to
|
||
not convert <code>NA</code> to <code>"NA"</code>; this leads to better
|
||
printing.)</p>
|
||
<p>The format method is also used by data frames, tibbles, and
|
||
<code>str()</code>:</p>
|
||
<div class="sourceCode" id="cb8"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb8-1"><a href="#cb8-1" tabindex="-1"></a><span class="fu">data.frame</span>(x)</span>
|
||
<span id="cb8-2"><a href="#cb8-2" tabindex="-1"></a><span class="co">#> x</span></span>
|
||
<span id="cb8-3"><a href="#cb8-3" tabindex="-1"></a><span class="co">#> 1 0%</span></span>
|
||
<span id="cb8-4"><a href="#cb8-4" tabindex="-1"></a><span class="co">#> 2 33.3%</span></span>
|
||
<span id="cb8-5"><a href="#cb8-5" tabindex="-1"></a><span class="co">#> 3 66.7%</span></span>
|
||
<span id="cb8-6"><a href="#cb8-6" tabindex="-1"></a><span class="co">#> 4 100%</span></span>
|
||
<span id="cb8-7"><a href="#cb8-7" tabindex="-1"></a><span class="co">#> 5 <NA></span></span></code></pre></div>
|
||
<p>For optimal display, I recommend also defining an abbreviated type
|
||
name, which should be 4-5 letters for commonly used vectors. This is
|
||
used in tibbles and in <code>str()</code>:</p>
|
||
<div class="sourceCode" id="cb9"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb9-1"><a href="#cb9-1" tabindex="-1"></a>vec_ptype_abbr.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb9-2"><a href="#cb9-2" tabindex="-1"></a> <span class="st">"prcnt"</span></span>
|
||
<span id="cb9-3"><a href="#cb9-3" tabindex="-1"></a>}</span>
|
||
<span id="cb9-4"><a href="#cb9-4" tabindex="-1"></a></span>
|
||
<span id="cb9-5"><a href="#cb9-5" tabindex="-1"></a>tibble<span class="sc">::</span><span class="fu">tibble</span>(x)</span>
|
||
<span id="cb9-6"><a href="#cb9-6" tabindex="-1"></a><span class="co">#> # A tibble: 5 × 1</span></span>
|
||
<span id="cb9-7"><a href="#cb9-7" tabindex="-1"></a><span class="co">#> x</span></span>
|
||
<span id="cb9-8"><a href="#cb9-8" tabindex="-1"></a><span class="co">#> <prcnt></span></span>
|
||
<span id="cb9-9"><a href="#cb9-9" tabindex="-1"></a><span class="co">#> 1 0%</span></span>
|
||
<span id="cb9-10"><a href="#cb9-10" tabindex="-1"></a><span class="co">#> 2 33.3%</span></span>
|
||
<span id="cb9-11"><a href="#cb9-11" tabindex="-1"></a><span class="co">#> 3 66.7%</span></span>
|
||
<span id="cb9-12"><a href="#cb9-12" tabindex="-1"></a><span class="co">#> 4 100%</span></span>
|
||
<span id="cb9-13"><a href="#cb9-13" tabindex="-1"></a><span class="co">#> 5 NA</span></span>
|
||
<span id="cb9-14"><a href="#cb9-14" tabindex="-1"></a></span>
|
||
<span id="cb9-15"><a href="#cb9-15" tabindex="-1"></a><span class="fu">str</span>(x)</span>
|
||
<span id="cb9-16"><a href="#cb9-16" tabindex="-1"></a><span class="co">#> prcnt [1:5] 0%, 33.3%, 66.7%, 100%, <NA></span></span></code></pre></div>
|
||
<p>If you need more control over printing in tibbles, implement a method
|
||
for <code>pillar::pillar_shaft()</code>. See
|
||
<code>vignette("pillar", package = "vctrs")</code> for details.</p>
|
||
</div>
|
||
</div>
|
||
<div id="casting-and-coercion" class="section level2">
|
||
<h2>Casting and coercion</h2>
|
||
<p>The next set of methods you are likely to need are those related to
|
||
coercion and casting. Coercion and casting are two sides of the same
|
||
coin: changing the prototype of an existing object. When the change
|
||
happens <em>implicitly</em> (e.g in <code>c()</code>) we call it
|
||
<strong>coercion</strong>; when the change happens <em>explicitly</em>
|
||
(e.g. with <code>as.integer(x)</code>), we call it
|
||
<strong>casting</strong>.</p>
|
||
<p>One of the main goals of vctrs is to put coercion and casting on a
|
||
robust theoretical footing so it’s possible to make accurate predictions
|
||
about what (e.g.) <code>c(x, y)</code> should do when <code>x</code> and
|
||
<code>y</code> have different prototypes. vctrs achieves this goal
|
||
through two generics:</p>
|
||
<ul>
|
||
<li><p><code>vec_ptype2(x, y)</code> defines possible set of coercions.
|
||
It returns a prototype if <code>x</code> and <code>y</code> can be
|
||
safely coerced to the same prototype; otherwise it returns an error. The
|
||
set of automatic coercions is usually quite small because too many tend
|
||
to make code harder to reason about and silently propagate
|
||
mistakes.</p></li>
|
||
<li><p><code>vec_cast(x, to)</code> defines the possible sets of casts.
|
||
It returns <code>x</code> translated to have prototype <code>to</code>,
|
||
or throws an error if the conversion isn’t possible. The set of possible
|
||
casts is a superset of possible coercions because they’re requested
|
||
explicitly.</p></li>
|
||
</ul>
|
||
<div id="double-dispatch" class="section level3">
|
||
<h3>Double dispatch</h3>
|
||
<p>Both generics use <a href="https://en.wikipedia.org/wiki/Double_dispatch"><strong>double
|
||
dispatch</strong></a> which means that the implementation is selected
|
||
based on the class of two arguments, not just one. S3 does not natively
|
||
support double dispatch, so we implement our own dispatch mechanism. In
|
||
practice, this means:</p>
|
||
<ul>
|
||
<li><p>You end up with method names with two classes, like
|
||
<code>vec_ptype2.foo.bar()</code>.</p></li>
|
||
<li><p>You don’t need to implement default methods (they would never be
|
||
called if you do).</p></li>
|
||
<li><p>You can’t call <code>NextMethod()</code>.</p></li>
|
||
</ul>
|
||
</div>
|
||
<div id="percent" class="section level3">
|
||
<h3>Percent class</h3>
|
||
<p>We’ll make our percent class coercible back and forth with double
|
||
vectors.</p>
|
||
<p><code>vec_ptype2()</code> provides a user friendly error message if
|
||
the coercion doesn’t exist and makes sure <code>NA</code> is handled in
|
||
a standard way. <code>NA</code> is technically a logical vector, but we
|
||
want to stand in for a missing value of any type.</p>
|
||
<div class="sourceCode" id="cb10"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb10-1"><a href="#cb10-1" tabindex="-1"></a><span class="fu">vec_ptype2</span>(<span class="st">"bogus"</span>, <span class="fu">percent</span>())</span>
|
||
<span id="cb10-2"><a href="#cb10-2" tabindex="-1"></a><span class="co">#> Error:</span></span>
|
||
<span id="cb10-3"><a href="#cb10-3" tabindex="-1"></a><span class="co">#> ! Can't combine `"bogus"` <character> and `percent()` <vctrs_percent>.</span></span>
|
||
<span id="cb10-4"><a href="#cb10-4" tabindex="-1"></a><span class="fu">vec_ptype2</span>(<span class="fu">percent</span>(), <span class="cn">NA</span>)</span>
|
||
<span id="cb10-5"><a href="#cb10-5" tabindex="-1"></a><span class="co">#> <vctrs_percent[0]></span></span>
|
||
<span id="cb10-6"><a href="#cb10-6" tabindex="-1"></a><span class="fu">vec_ptype2</span>(<span class="cn">NA</span>, <span class="fu">percent</span>())</span>
|
||
<span id="cb10-7"><a href="#cb10-7" tabindex="-1"></a><span class="co">#> <vctrs_percent[0]></span></span></code></pre></div>
|
||
<p>By default and in simple cases, an object of the same class is
|
||
compatible with itself:</p>
|
||
<div class="sourceCode" id="cb11"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb11-1"><a href="#cb11-1" tabindex="-1"></a><span class="fu">vec_ptype2</span>(<span class="fu">percent</span>(), <span class="fu">percent</span>())</span>
|
||
<span id="cb11-2"><a href="#cb11-2" tabindex="-1"></a><span class="co">#> <vctrs_percent[0]></span></span></code></pre></div>
|
||
<p>However this only works if the attributes for both objects are the
|
||
same. Also the default methods are a bit slower. It is always a good
|
||
idea to provide an explicit coercion method for the case of identical
|
||
classes. So we’ll start by saying that a <code>vctrs_percent</code>
|
||
combined with a <code>vctrs_percent</code> yields a
|
||
<code>vctrs_percent</code>, which we indicate by returning a prototype
|
||
generated by the constructor.</p>
|
||
<div class="sourceCode" id="cb12"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb12-1"><a href="#cb12-1" tabindex="-1"></a>vec_ptype2.vctrs_percent.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">new_percent</span>()</span></code></pre></div>
|
||
<p>Next we define methods that say that combining a <code>percent</code>
|
||
and double should yield a <code>double</code>. We avoid returning a
|
||
<code>percent</code> here because errors in the scale (1 vs. 0.01) are
|
||
more obvious with raw numbers.</p>
|
||
<p>Because double dispatch is a bit of a hack, we need to provide two
|
||
methods. It’s your responsibility to ensure that each member of the pair
|
||
returns the same result: if they don’t you will get weird and
|
||
unpredictable behaviour.</p>
|
||
<p>The double dispatch mechanism requires us to refer to the underlying
|
||
type, <code>double</code>, in the method name. If we implemented
|
||
<code>vec_ptype2.vctrs_percent.numeric()</code>, it would never be
|
||
called.</p>
|
||
<div class="sourceCode" id="cb13"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb13-1"><a href="#cb13-1" tabindex="-1"></a>vec_ptype2.vctrs_percent.double <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">double</span>()</span>
|
||
<span id="cb13-2"><a href="#cb13-2" tabindex="-1"></a>vec_ptype2.double.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">double</span>()</span></code></pre></div>
|
||
<p>We can check that we’ve implemented this correctly with
|
||
<code>vec_ptype_show()</code>:</p>
|
||
<div class="sourceCode" id="cb14"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb14-1"><a href="#cb14-1" tabindex="-1"></a><span class="fu">vec_ptype_show</span>(<span class="fu">percent</span>(), <span class="fu">double</span>(), <span class="fu">percent</span>())</span>
|
||
<span id="cb14-2"><a href="#cb14-2" tabindex="-1"></a><span class="co">#> Prototype: <double></span></span>
|
||
<span id="cb14-3"><a href="#cb14-3" tabindex="-1"></a><span class="co">#> 0. ( , <vctrs_percent> ) = <vctrs_percent></span></span>
|
||
<span id="cb14-4"><a href="#cb14-4" tabindex="-1"></a><span class="co">#> 1. ( <vctrs_percent> , <double> ) = <double> </span></span>
|
||
<span id="cb14-5"><a href="#cb14-5" tabindex="-1"></a><span class="co">#> 2. ( <double> , <vctrs_percent> ) = <double></span></span></code></pre></div>
|
||
<p>The <code>vec_ptype2()</code> methods define which input is the
|
||
richer type that vctrs should coerce to. However, they don’t perform any
|
||
conversion. This is the job of <code>vec_cast()</code>, which we
|
||
implement next. We’ll provide a method to cast a percent to a
|
||
percent:</p>
|
||
<div class="sourceCode" id="cb15"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb15-1"><a href="#cb15-1" tabindex="-1"></a>vec_cast.vctrs_percent.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) x</span></code></pre></div>
|
||
<p>And then for converting back and forth between doubles. To convert a
|
||
double to a percent we use the <code>percent()</code> helper (not the
|
||
constructor; this is unvalidated user input). To convert a
|
||
<code>percent</code> to a double, we strip the attributes.</p>
|
||
<p>Note that for historical reasons the order of argument in the
|
||
signature is the opposite as for <code>vec_ptype2()</code>. The class
|
||
for <code>to</code> comes first, and the class for <code>x</code> comes
|
||
second.</p>
|
||
<p>Again, the double dispatch mechanism requires us to refer to the
|
||
underlying type, <code>double</code>, in the method name. Implementing
|
||
<code>vec_cast.vctrs_percent.numeric()</code> has no effect.</p>
|
||
<div class="sourceCode" id="cb16"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb16-1"><a href="#cb16-1" tabindex="-1"></a>vec_cast.vctrs_percent.double <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">percent</span>(x)</span>
|
||
<span id="cb16-2"><a href="#cb16-2" tabindex="-1"></a>vec_cast.double.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">vec_data</span>(x)</span></code></pre></div>
|
||
<p>Then we can check this works with <code>vec_cast()</code>:</p>
|
||
<div class="sourceCode" id="cb17"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb17-1"><a href="#cb17-1" tabindex="-1"></a><span class="fu">vec_cast</span>(<span class="fl">0.5</span>, <span class="fu">percent</span>())</span>
|
||
<span id="cb17-2"><a href="#cb17-2" tabindex="-1"></a><span class="co">#> <vctrs_percent[1]></span></span>
|
||
<span id="cb17-3"><a href="#cb17-3" tabindex="-1"></a><span class="co">#> [1] 50%</span></span>
|
||
<span id="cb17-4"><a href="#cb17-4" tabindex="-1"></a><span class="fu">vec_cast</span>(<span class="fu">percent</span>(<span class="fl">0.5</span>), <span class="fu">double</span>())</span>
|
||
<span id="cb17-5"><a href="#cb17-5" tabindex="-1"></a><span class="co">#> [1] 0.5</span></span></code></pre></div>
|
||
<p>Once you’ve implemented <code>vec_ptype2()</code> and
|
||
<code>vec_cast()</code>, you get <code>vec_c()</code>,
|
||
<code>[<-</code>, and <code>[[<-</code> implementations for
|
||
free.</p>
|
||
<div class="sourceCode" id="cb18"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb18-1"><a href="#cb18-1" tabindex="-1"></a><span class="fu">vec_c</span>(<span class="fu">percent</span>(<span class="fl">0.5</span>), <span class="dv">1</span>)</span>
|
||
<span id="cb18-2"><a href="#cb18-2" tabindex="-1"></a><span class="co">#> [1] 0.5 1.0</span></span>
|
||
<span id="cb18-3"><a href="#cb18-3" tabindex="-1"></a><span class="fu">vec_c</span>(<span class="cn">NA</span>, <span class="fu">percent</span>(<span class="fl">0.5</span>))</span>
|
||
<span id="cb18-4"><a href="#cb18-4" tabindex="-1"></a><span class="co">#> <vctrs_percent[2]></span></span>
|
||
<span id="cb18-5"><a href="#cb18-5" tabindex="-1"></a><span class="co">#> [1] <NA> 50%</span></span>
|
||
<span id="cb18-6"><a href="#cb18-6" tabindex="-1"></a><span class="co"># but</span></span>
|
||
<span id="cb18-7"><a href="#cb18-7" tabindex="-1"></a><span class="fu">vec_c</span>(<span class="cn">TRUE</span>, <span class="fu">percent</span>(<span class="fl">0.5</span>))</span>
|
||
<span id="cb18-8"><a href="#cb18-8" tabindex="-1"></a><span class="co">#> Error in `vec_c()`:</span></span>
|
||
<span id="cb18-9"><a href="#cb18-9" tabindex="-1"></a><span class="co">#> ! Can't combine `..1` <logical> and `..2` <vctrs_percent>.</span></span>
|
||
<span id="cb18-10"><a href="#cb18-10" tabindex="-1"></a></span>
|
||
<span id="cb18-11"><a href="#cb18-11" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">percent</span>(<span class="fu">c</span>(<span class="fl">0.5</span>, <span class="dv">1</span>, <span class="dv">2</span>))</span>
|
||
<span id="cb18-12"><a href="#cb18-12" tabindex="-1"></a>x[<span class="dv">1</span><span class="sc">:</span><span class="dv">2</span>] <span class="ot"><-</span> <span class="dv">2</span><span class="sc">:</span><span class="dv">1</span></span>
|
||
<span id="cb18-13"><a href="#cb18-13" tabindex="-1"></a><span class="co">#> Error in `vec_restore_dispatch()`:</span></span>
|
||
<span id="cb18-14"><a href="#cb18-14" tabindex="-1"></a><span class="co">#> ! Can't convert <integer> to <vctrs_percent>.</span></span>
|
||
<span id="cb18-15"><a href="#cb18-15" tabindex="-1"></a>x[[<span class="dv">3</span>]] <span class="ot"><-</span> <span class="fl">0.5</span></span>
|
||
<span id="cb18-16"><a href="#cb18-16" tabindex="-1"></a>x</span>
|
||
<span id="cb18-17"><a href="#cb18-17" tabindex="-1"></a><span class="co">#> <vctrs_percent[3]></span></span>
|
||
<span id="cb18-18"><a href="#cb18-18" tabindex="-1"></a><span class="co">#> [1] 50% 100% 50%</span></span></code></pre></div>
|
||
<p>You’ll also get mostly correct behaviour for <code>c()</code>. The
|
||
exception is when you use <code>c()</code> with a base R class:</p>
|
||
<div class="sourceCode" id="cb19"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb19-1"><a href="#cb19-1" tabindex="-1"></a><span class="co"># Correct</span></span>
|
||
<span id="cb19-2"><a href="#cb19-2" tabindex="-1"></a><span class="fu">c</span>(<span class="fu">percent</span>(<span class="fl">0.5</span>), <span class="dv">1</span>)</span>
|
||
<span id="cb19-3"><a href="#cb19-3" tabindex="-1"></a><span class="co">#> [1] 0.5 1.0</span></span>
|
||
<span id="cb19-4"><a href="#cb19-4" tabindex="-1"></a><span class="fu">c</span>(<span class="fu">percent</span>(<span class="fl">0.5</span>), <span class="fu">factor</span>(<span class="dv">1</span>))</span>
|
||
<span id="cb19-5"><a href="#cb19-5" tabindex="-1"></a><span class="co">#> Error in `vec_c()`:</span></span>
|
||
<span id="cb19-6"><a href="#cb19-6" tabindex="-1"></a><span class="co">#> ! Can't combine `..1` <vctrs_percent> and `..2` <factor<25c7e>>.</span></span>
|
||
<span id="cb19-7"><a href="#cb19-7" tabindex="-1"></a></span>
|
||
<span id="cb19-8"><a href="#cb19-8" tabindex="-1"></a><span class="co"># Incorrect</span></span>
|
||
<span id="cb19-9"><a href="#cb19-9" tabindex="-1"></a><span class="fu">c</span>(<span class="fu">factor</span>(<span class="dv">1</span>), <span class="fu">percent</span>(<span class="fl">0.5</span>))</span>
|
||
<span id="cb19-10"><a href="#cb19-10" tabindex="-1"></a><span class="co">#> [1] 1.0 0.5</span></span></code></pre></div>
|
||
<p>Unfortunately there’s no way to fix this problem with the current
|
||
design of <code>c()</code>.</p>
|
||
<p>Again, as a convenience, consider providing an
|
||
<code>as_percent()</code> function that makes use of the casts defined
|
||
in your <code>vec_cast.vctrs_percent()</code> methods:</p>
|
||
<div class="sourceCode" id="cb20"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb20-1"><a href="#cb20-1" tabindex="-1"></a>as_percent <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb20-2"><a href="#cb20-2" tabindex="-1"></a> <span class="fu">vec_cast</span>(x, <span class="fu">new_percent</span>())</span>
|
||
<span id="cb20-3"><a href="#cb20-3" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Occasionally, it is useful to provide conversions that go beyond
|
||
what’s allowed in casting. For example, we could offer a parsing method
|
||
for character vectors. In this case, <code>as_percent()</code> should be
|
||
generic, the default method should cast, and then additional methods
|
||
should implement more flexible conversion:</p>
|
||
<div class="sourceCode" id="cb21"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb21-1"><a href="#cb21-1" tabindex="-1"></a>as_percent <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb21-2"><a href="#cb21-2" tabindex="-1"></a> <span class="fu">UseMethod</span>(<span class="st">"as_percent"</span>)</span>
|
||
<span id="cb21-3"><a href="#cb21-3" tabindex="-1"></a>}</span>
|
||
<span id="cb21-4"><a href="#cb21-4" tabindex="-1"></a></span>
|
||
<span id="cb21-5"><a href="#cb21-5" tabindex="-1"></a>as_percent.default <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb21-6"><a href="#cb21-6" tabindex="-1"></a> <span class="fu">vec_cast</span>(x, <span class="fu">new_percent</span>())</span>
|
||
<span id="cb21-7"><a href="#cb21-7" tabindex="-1"></a>}</span>
|
||
<span id="cb21-8"><a href="#cb21-8" tabindex="-1"></a></span>
|
||
<span id="cb21-9"><a href="#cb21-9" tabindex="-1"></a>as_percent.character <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb21-10"><a href="#cb21-10" tabindex="-1"></a> value <span class="ot"><-</span> <span class="fu">as.numeric</span>(<span class="fu">gsub</span>(<span class="st">" *% *$"</span>, <span class="st">""</span>, x)) <span class="sc">/</span> <span class="dv">100</span></span>
|
||
<span id="cb21-11"><a href="#cb21-11" tabindex="-1"></a> <span class="fu">new_percent</span>(value)</span>
|
||
<span id="cb21-12"><a href="#cb21-12" tabindex="-1"></a>}</span></code></pre></div>
|
||
</div>
|
||
<div id="decimal-class" class="section level3">
|
||
<h3>Decimal class</h3>
|
||
<p>Now that you’ve seen the basics with a very simple S3 class, we’ll
|
||
gradually explore more complicated scenarios. This section creates a
|
||
<code>decimal</code> class that prints with the specified number of
|
||
decimal places. This is very similar to <code>percent</code> but now the
|
||
class needs an attribute: the number of decimal places to display (an
|
||
integer vector of length 1).</p>
|
||
<p>We start off as before, defining a low-level constructor, a
|
||
user-friendly constructor, a <code>format()</code> method, and a
|
||
<code>vec_ptype_abbr()</code>. Note that additional object attributes
|
||
are simply passed along to <code>new_vctr()</code>:</p>
|
||
<div class="sourceCode" id="cb22"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb22-1"><a href="#cb22-1" tabindex="-1"></a>new_decimal <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>(), <span class="at">digits =</span> <span class="dv">2</span><span class="dt">L</span>) {</span>
|
||
<span id="cb22-2"><a href="#cb22-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_double</span>(x)) {</span>
|
||
<span id="cb22-3"><a href="#cb22-3" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`x` must be a double vector."</span>)</span>
|
||
<span id="cb22-4"><a href="#cb22-4" tabindex="-1"></a> }</span>
|
||
<span id="cb22-5"><a href="#cb22-5" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_integer</span>(digits)) {</span>
|
||
<span id="cb22-6"><a href="#cb22-6" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`digits` must be an integer vector."</span>)</span>
|
||
<span id="cb22-7"><a href="#cb22-7" tabindex="-1"></a> }</span>
|
||
<span id="cb22-8"><a href="#cb22-8" tabindex="-1"></a> <span class="fu">vec_check_size</span>(digits, <span class="at">size =</span> <span class="dv">1</span><span class="dt">L</span>)</span>
|
||
<span id="cb22-9"><a href="#cb22-9" tabindex="-1"></a></span>
|
||
<span id="cb22-10"><a href="#cb22-10" tabindex="-1"></a> <span class="fu">new_vctr</span>(x, <span class="at">digits =</span> digits, <span class="at">class =</span> <span class="st">"vctrs_decimal"</span>)</span>
|
||
<span id="cb22-11"><a href="#cb22-11" tabindex="-1"></a>}</span>
|
||
<span id="cb22-12"><a href="#cb22-12" tabindex="-1"></a></span>
|
||
<span id="cb22-13"><a href="#cb22-13" tabindex="-1"></a>decimal <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>(), <span class="at">digits =</span> <span class="dv">2</span><span class="dt">L</span>) {</span>
|
||
<span id="cb22-14"><a href="#cb22-14" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast</span>(x, <span class="fu">double</span>())</span>
|
||
<span id="cb22-15"><a href="#cb22-15" tabindex="-1"></a> digits <span class="ot"><-</span> <span class="fu">vec_recycle</span>(<span class="fu">vec_cast</span>(digits, <span class="fu">integer</span>()), <span class="dv">1</span><span class="dt">L</span>)</span>
|
||
<span id="cb22-16"><a href="#cb22-16" tabindex="-1"></a></span>
|
||
<span id="cb22-17"><a href="#cb22-17" tabindex="-1"></a> <span class="fu">new_decimal</span>(x, <span class="at">digits =</span> digits)</span>
|
||
<span id="cb22-18"><a href="#cb22-18" tabindex="-1"></a>}</span>
|
||
<span id="cb22-19"><a href="#cb22-19" tabindex="-1"></a></span>
|
||
<span id="cb22-20"><a href="#cb22-20" tabindex="-1"></a>digits <span class="ot"><-</span> <span class="cf">function</span>(x) <span class="fu">attr</span>(x, <span class="st">"digits"</span>)</span>
|
||
<span id="cb22-21"><a href="#cb22-21" tabindex="-1"></a></span>
|
||
<span id="cb22-22"><a href="#cb22-22" tabindex="-1"></a>format.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb22-23"><a href="#cb22-23" tabindex="-1"></a> <span class="fu">sprintf</span>(<span class="fu">paste0</span>(<span class="st">"%-0."</span>, <span class="fu">digits</span>(x), <span class="st">"f"</span>), x)</span>
|
||
<span id="cb22-24"><a href="#cb22-24" tabindex="-1"></a>}</span>
|
||
<span id="cb22-25"><a href="#cb22-25" tabindex="-1"></a></span>
|
||
<span id="cb22-26"><a href="#cb22-26" tabindex="-1"></a>vec_ptype_abbr.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb22-27"><a href="#cb22-27" tabindex="-1"></a> <span class="st">"dec"</span></span>
|
||
<span id="cb22-28"><a href="#cb22-28" tabindex="-1"></a>}</span>
|
||
<span id="cb22-29"><a href="#cb22-29" tabindex="-1"></a></span>
|
||
<span id="cb22-30"><a href="#cb22-30" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">decimal</span>(<span class="fu">runif</span>(<span class="dv">10</span>), <span class="dv">1</span><span class="dt">L</span>)</span>
|
||
<span id="cb22-31"><a href="#cb22-31" tabindex="-1"></a>x</span>
|
||
<span id="cb22-32"><a href="#cb22-32" tabindex="-1"></a><span class="co">#> <vctrs_decimal[10]></span></span>
|
||
<span id="cb22-33"><a href="#cb22-33" tabindex="-1"></a><span class="co">#> [1] 0.1 0.8 0.6 0.2 0.0 0.5 0.5 0.3 0.7 0.8</span></span></code></pre></div>
|
||
<p>Note that I provide a little helper to extract the
|
||
<code>digits</code> attribute. This makes the code a little easier to
|
||
read and should not be exported.</p>
|
||
<p>By default, vctrs assumes that attributes are independent of the data
|
||
and so are automatically preserved. You’ll see what to do if the
|
||
attributes are data dependent in the next section.</p>
|
||
<div class="sourceCode" id="cb23"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb23-1"><a href="#cb23-1" tabindex="-1"></a>x[<span class="dv">1</span><span class="sc">:</span><span class="dv">2</span>]</span>
|
||
<span id="cb23-2"><a href="#cb23-2" tabindex="-1"></a><span class="co">#> <vctrs_decimal[2]></span></span>
|
||
<span id="cb23-3"><a href="#cb23-3" tabindex="-1"></a><span class="co">#> [1] 0.1 0.8</span></span>
|
||
<span id="cb23-4"><a href="#cb23-4" tabindex="-1"></a>x[[<span class="dv">1</span>]]</span>
|
||
<span id="cb23-5"><a href="#cb23-5" tabindex="-1"></a><span class="co">#> <vctrs_decimal[1]></span></span>
|
||
<span id="cb23-6"><a href="#cb23-6" tabindex="-1"></a><span class="co">#> [1] 0.1</span></span></code></pre></div>
|
||
<p>For the sake of exposition, we’ll assume that <code>digits</code> is
|
||
an important attribute of the class and should be included in the full
|
||
type:</p>
|
||
<div class="sourceCode" id="cb24"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb24-1"><a href="#cb24-1" tabindex="-1"></a>vec_ptype_full.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb24-2"><a href="#cb24-2" tabindex="-1"></a> <span class="fu">paste0</span>(<span class="st">"decimal<"</span>, <span class="fu">digits</span>(x), <span class="st">">"</span>)</span>
|
||
<span id="cb24-3"><a href="#cb24-3" tabindex="-1"></a>}</span>
|
||
<span id="cb24-4"><a href="#cb24-4" tabindex="-1"></a></span>
|
||
<span id="cb24-5"><a href="#cb24-5" tabindex="-1"></a>x</span>
|
||
<span id="cb24-6"><a href="#cb24-6" tabindex="-1"></a><span class="co">#> <decimal<1>[10]></span></span>
|
||
<span id="cb24-7"><a href="#cb24-7" tabindex="-1"></a><span class="co">#> [1] 0.1 0.8 0.6 0.2 0.0 0.5 0.5 0.3 0.7 0.8</span></span></code></pre></div>
|
||
<p>Now consider <code>vec_cast()</code> and <code>vec_ptype2()</code>.
|
||
Casting and coercing from one decimal to another requires a little
|
||
thought as the values of the <code>digits</code> attribute might be
|
||
different, and we need some way to reconcile them. Here I’ve decided to
|
||
chose the maximum of the two; other reasonable options are to take the
|
||
value from the left-hand side or throw an error.</p>
|
||
<div class="sourceCode" id="cb25"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb25-1"><a href="#cb25-1" tabindex="-1"></a>vec_ptype2.vctrs_decimal.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) {</span>
|
||
<span id="cb25-2"><a href="#cb25-2" tabindex="-1"></a> <span class="fu">new_decimal</span>(<span class="at">digits =</span> <span class="fu">max</span>(<span class="fu">digits</span>(x), <span class="fu">digits</span>(y)))</span>
|
||
<span id="cb25-3"><a href="#cb25-3" tabindex="-1"></a>}</span>
|
||
<span id="cb25-4"><a href="#cb25-4" tabindex="-1"></a>vec_cast.vctrs_decimal.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) {</span>
|
||
<span id="cb25-5"><a href="#cb25-5" tabindex="-1"></a> <span class="fu">new_decimal</span>(<span class="fu">vec_data</span>(x), <span class="at">digits =</span> <span class="fu">digits</span>(to))</span>
|
||
<span id="cb25-6"><a href="#cb25-6" tabindex="-1"></a>}</span>
|
||
<span id="cb25-7"><a href="#cb25-7" tabindex="-1"></a></span>
|
||
<span id="cb25-8"><a href="#cb25-8" tabindex="-1"></a><span class="fu">vec_c</span>(<span class="fu">decimal</span>(<span class="dv">1</span><span class="sc">/</span><span class="dv">100</span>, <span class="at">digits =</span> <span class="dv">3</span>), <span class="fu">decimal</span>(<span class="dv">2</span><span class="sc">/</span><span class="dv">100</span>, <span class="at">digits =</span> <span class="dv">2</span>))</span>
|
||
<span id="cb25-9"><a href="#cb25-9" tabindex="-1"></a><span class="co">#> <decimal<3>[2]></span></span>
|
||
<span id="cb25-10"><a href="#cb25-10" tabindex="-1"></a><span class="co">#> [1] 0.010 0.020</span></span></code></pre></div>
|
||
<p>Finally, I can implement coercion to and from other types, like
|
||
doubles. When automatically coercing, I choose the richer type (i.e.,
|
||
the decimal).</p>
|
||
<div class="sourceCode" id="cb26"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb26-1"><a href="#cb26-1" tabindex="-1"></a>vec_ptype2.vctrs_decimal.double <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) x</span>
|
||
<span id="cb26-2"><a href="#cb26-2" tabindex="-1"></a>vec_ptype2.double.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) y</span>
|
||
<span id="cb26-3"><a href="#cb26-3" tabindex="-1"></a></span>
|
||
<span id="cb26-4"><a href="#cb26-4" tabindex="-1"></a>vec_cast.vctrs_decimal.double <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">new_decimal</span>(x, <span class="at">digits =</span> <span class="fu">digits</span>(to))</span>
|
||
<span id="cb26-5"><a href="#cb26-5" tabindex="-1"></a>vec_cast.double.vctrs_decimal <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">vec_data</span>(x)</span>
|
||
<span id="cb26-6"><a href="#cb26-6" tabindex="-1"></a></span>
|
||
<span id="cb26-7"><a href="#cb26-7" tabindex="-1"></a><span class="fu">vec_c</span>(<span class="fu">decimal</span>(<span class="dv">1</span>, <span class="at">digits =</span> <span class="dv">1</span>), pi)</span>
|
||
<span id="cb26-8"><a href="#cb26-8" tabindex="-1"></a><span class="co">#> <decimal<1>[2]></span></span>
|
||
<span id="cb26-9"><a href="#cb26-9" tabindex="-1"></a><span class="co">#> [1] 1.0 3.1</span></span>
|
||
<span id="cb26-10"><a href="#cb26-10" tabindex="-1"></a><span class="fu">vec_c</span>(pi, <span class="fu">decimal</span>(<span class="dv">1</span>, <span class="at">digits =</span> <span class="dv">1</span>))</span>
|
||
<span id="cb26-11"><a href="#cb26-11" tabindex="-1"></a><span class="co">#> <decimal<1>[2]></span></span>
|
||
<span id="cb26-12"><a href="#cb26-12" tabindex="-1"></a><span class="co">#> [1] 3.1 1.0</span></span></code></pre></div>
|
||
<p>If type <code>x</code> has greater resolution than <code>y</code>,
|
||
there will be some inputs that lose precision. These should generate
|
||
errors using <code>stop_lossy_cast()</code>. You can see that in action
|
||
when casting from doubles to integers; only some doubles can become
|
||
integers without losing resolution.</p>
|
||
<div class="sourceCode" id="cb27"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb27-1"><a href="#cb27-1" tabindex="-1"></a><span class="fu">vec_cast</span>(<span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">10</span>), <span class="at">to =</span> <span class="fu">integer</span>())</span>
|
||
<span id="cb27-2"><a href="#cb27-2" tabindex="-1"></a><span class="co">#> [1] 1 2 10</span></span>
|
||
<span id="cb27-3"><a href="#cb27-3" tabindex="-1"></a></span>
|
||
<span id="cb27-4"><a href="#cb27-4" tabindex="-1"></a><span class="fu">vec_cast</span>(<span class="fu">c</span>(<span class="fl">1.5</span>, <span class="dv">2</span>, <span class="fl">10.5</span>), <span class="at">to =</span> <span class="fu">integer</span>())</span>
|
||
<span id="cb27-5"><a href="#cb27-5" tabindex="-1"></a><span class="co">#> Error:</span></span>
|
||
<span id="cb27-6"><a href="#cb27-6" tabindex="-1"></a><span class="co">#> ! Can't convert from `c(1.5, 2, 10.5)` <double> to <integer> due to loss of precision.</span></span>
|
||
<span id="cb27-7"><a href="#cb27-7" tabindex="-1"></a><span class="co">#> • Locations: 1, 3</span></span></code></pre></div>
|
||
</div>
|
||
<div id="cached-sum" class="section level3">
|
||
<h3>Cached sum class</h3>
|
||
<p>The next level up in complexity is an object that has data-dependent
|
||
attributes. To explore this idea we’ll create a vector that caches the
|
||
sum of its values. As usual, we start with low-level and user-friendly
|
||
constructors:</p>
|
||
<div class="sourceCode" id="cb28"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb28-1"><a href="#cb28-1" tabindex="-1"></a>new_cached_sum <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>(), <span class="at">sum =</span> <span class="dv">0</span><span class="dt">L</span>) {</span>
|
||
<span id="cb28-2"><a href="#cb28-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_double</span>(x)) {</span>
|
||
<span id="cb28-3"><a href="#cb28-3" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`x` must be a double vector."</span>)</span>
|
||
<span id="cb28-4"><a href="#cb28-4" tabindex="-1"></a> }</span>
|
||
<span id="cb28-5"><a href="#cb28-5" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_double</span>(sum)) {</span>
|
||
<span id="cb28-6"><a href="#cb28-6" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`sum` must be a double vector."</span>)</span>
|
||
<span id="cb28-7"><a href="#cb28-7" tabindex="-1"></a> }</span>
|
||
<span id="cb28-8"><a href="#cb28-8" tabindex="-1"></a> <span class="fu">vec_check_size</span>(sum, <span class="at">size =</span> <span class="dv">1</span><span class="dt">L</span>)</span>
|
||
<span id="cb28-9"><a href="#cb28-9" tabindex="-1"></a></span>
|
||
<span id="cb28-10"><a href="#cb28-10" tabindex="-1"></a> <span class="fu">new_vctr</span>(x, <span class="at">sum =</span> sum, <span class="at">class =</span> <span class="st">"vctrs_cached_sum"</span>)</span>
|
||
<span id="cb28-11"><a href="#cb28-11" tabindex="-1"></a>}</span>
|
||
<span id="cb28-12"><a href="#cb28-12" tabindex="-1"></a></span>
|
||
<span id="cb28-13"><a href="#cb28-13" tabindex="-1"></a>cached_sum <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb28-14"><a href="#cb28-14" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast</span>(x, <span class="fu">double</span>())</span>
|
||
<span id="cb28-15"><a href="#cb28-15" tabindex="-1"></a> <span class="fu">new_cached_sum</span>(x, <span class="fu">sum</span>(x))</span>
|
||
<span id="cb28-16"><a href="#cb28-16" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>For this class, we can use the default <code>format()</code> method,
|
||
and instead, we’ll customise the <code>obj_print_footer()</code> method.
|
||
This is a good place to display user facing attributes.</p>
|
||
<div class="sourceCode" id="cb29"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb29-1"><a href="#cb29-1" tabindex="-1"></a>obj_print_footer.vctrs_cached_sum <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb29-2"><a href="#cb29-2" tabindex="-1"></a> <span class="fu">cat</span>(<span class="st">"# Sum: "</span>, <span class="fu">format</span>(<span class="fu">attr</span>(x, <span class="st">"sum"</span>), <span class="at">digits =</span> <span class="dv">3</span>), <span class="st">"</span><span class="sc">\n</span><span class="st">"</span>, <span class="at">sep =</span> <span class="st">""</span>)</span>
|
||
<span id="cb29-3"><a href="#cb29-3" tabindex="-1"></a>}</span>
|
||
<span id="cb29-4"><a href="#cb29-4" tabindex="-1"></a></span>
|
||
<span id="cb29-5"><a href="#cb29-5" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">cached_sum</span>(<span class="fu">runif</span>(<span class="dv">10</span>))</span>
|
||
<span id="cb29-6"><a href="#cb29-6" tabindex="-1"></a>x</span>
|
||
<span id="cb29-7"><a href="#cb29-7" tabindex="-1"></a><span class="co">#> <vctrs_cached_sum[10]></span></span>
|
||
<span id="cb29-8"><a href="#cb29-8" tabindex="-1"></a><span class="co">#> [1] 0.87460066 0.17494063 0.03424133 0.32038573 0.40232824 0.19566983</span></span>
|
||
<span id="cb29-9"><a href="#cb29-9" tabindex="-1"></a><span class="co">#> [7] 0.40353812 0.06366146 0.38870131 0.97554784</span></span>
|
||
<span id="cb29-10"><a href="#cb29-10" tabindex="-1"></a><span class="co">#> # Sum: 3.83</span></span></code></pre></div>
|
||
<p>We’ll also override <code>sum()</code> and <code>mean()</code> to use
|
||
the attribute. This is easiest to do with <code>vec_math()</code>, which
|
||
you’ll learn about later.</p>
|
||
<div class="sourceCode" id="cb30"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb30-1"><a href="#cb30-1" tabindex="-1"></a>vec_math.vctrs_cached_sum <span class="ot"><-</span> <span class="cf">function</span>(.fn, .x, ...) {</span>
|
||
<span id="cb30-2"><a href="#cb30-2" tabindex="-1"></a> <span class="fu">cat</span>(<span class="st">"Using cache</span><span class="sc">\n</span><span class="st">"</span>)</span>
|
||
<span id="cb30-3"><a href="#cb30-3" tabindex="-1"></a> <span class="cf">switch</span>(.fn,</span>
|
||
<span id="cb30-4"><a href="#cb30-4" tabindex="-1"></a> <span class="at">sum =</span> <span class="fu">attr</span>(.x, <span class="st">"sum"</span>),</span>
|
||
<span id="cb30-5"><a href="#cb30-5" tabindex="-1"></a> <span class="at">mean =</span> <span class="fu">attr</span>(.x, <span class="st">"sum"</span>) <span class="sc">/</span> <span class="fu">length</span>(.x),</span>
|
||
<span id="cb30-6"><a href="#cb30-6" tabindex="-1"></a> <span class="fu">vec_math_base</span>(.fn, .x, ...)</span>
|
||
<span id="cb30-7"><a href="#cb30-7" tabindex="-1"></a> )</span>
|
||
<span id="cb30-8"><a href="#cb30-8" tabindex="-1"></a>}</span>
|
||
<span id="cb30-9"><a href="#cb30-9" tabindex="-1"></a></span>
|
||
<span id="cb30-10"><a href="#cb30-10" tabindex="-1"></a><span class="fu">sum</span>(x)</span>
|
||
<span id="cb30-11"><a href="#cb30-11" tabindex="-1"></a><span class="co">#> Using cache</span></span>
|
||
<span id="cb30-12"><a href="#cb30-12" tabindex="-1"></a><span class="co">#> [1] 3.833615</span></span></code></pre></div>
|
||
<p>As mentioned above, vctrs assumes that attributes are independent of
|
||
the data. This means that when we take advantage of the default methods,
|
||
they’ll work, but return the incorrect result:</p>
|
||
<div class="sourceCode" id="cb31"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb31-1"><a href="#cb31-1" tabindex="-1"></a>x[<span class="dv">1</span><span class="sc">:</span><span class="dv">2</span>]</span>
|
||
<span id="cb31-2"><a href="#cb31-2" tabindex="-1"></a><span class="co">#> <vctrs_cached_sum[2]></span></span>
|
||
<span id="cb31-3"><a href="#cb31-3" tabindex="-1"></a><span class="co">#> [1] 0.8746007 0.1749406</span></span>
|
||
<span id="cb31-4"><a href="#cb31-4" tabindex="-1"></a><span class="co">#> # Sum: 3.83</span></span></code></pre></div>
|
||
<p>To fix this, you need to provide a <code>vec_restore()</code> method.
|
||
Note that this method dispatches on the <code>to</code> argument.</p>
|
||
<div class="sourceCode" id="cb32"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb32-1"><a href="#cb32-1" tabindex="-1"></a>vec_restore.vctrs_cached_sum <span class="ot"><-</span> <span class="cf">function</span>(x, to, ..., <span class="at">i =</span> <span class="cn">NULL</span>) {</span>
|
||
<span id="cb32-2"><a href="#cb32-2" tabindex="-1"></a> <span class="fu">new_cached_sum</span>(x, <span class="fu">sum</span>(x))</span>
|
||
<span id="cb32-3"><a href="#cb32-3" tabindex="-1"></a>}</span>
|
||
<span id="cb32-4"><a href="#cb32-4" tabindex="-1"></a></span>
|
||
<span id="cb32-5"><a href="#cb32-5" tabindex="-1"></a>x[<span class="dv">1</span>]</span>
|
||
<span id="cb32-6"><a href="#cb32-6" tabindex="-1"></a><span class="co">#> <vctrs_cached_sum[1]></span></span>
|
||
<span id="cb32-7"><a href="#cb32-7" tabindex="-1"></a><span class="co">#> [1] 0.8746007</span></span>
|
||
<span id="cb32-8"><a href="#cb32-8" tabindex="-1"></a><span class="co">#> # Sum: 0.875</span></span></code></pre></div>
|
||
<p>This works because most of the vctrs methods dispatch to the
|
||
underlying base function by first stripping off extra attributes with
|
||
<code>vec_data()</code> and then reapplying them again with
|
||
<code>vec_restore()</code>. The default <code>vec_restore()</code>
|
||
method copies over all attributes, which is not appropriate when the
|
||
attributes depend on the data.</p>
|
||
<p>Note that <code>vec_restore.class</code> is subtly different from
|
||
<code>vec_cast.class.class()</code>. <code>vec_restore()</code> is used
|
||
when restoring attributes that have been lost; <code>vec_cast()</code>
|
||
is used for coercions. This is easier to understand with a concrete
|
||
example. Imagine factors were implemented with <code>new_vctr()</code>.
|
||
<code>vec_restore.factor()</code> would restore attributes back to an
|
||
integer vector, but you would not want to allow manually casting an
|
||
integer to a factor with <code>vec_cast()</code>.</p>
|
||
</div>
|
||
</div>
|
||
<div id="record-style-objects" class="section level2">
|
||
<h2>Record-style objects</h2>
|
||
<p>Record-style objects use a list of equal-length vectors to represent
|
||
individual components of the object. The best example of this is
|
||
<code>POSIXlt</code>, which underneath the hood is a list of 11 fields
|
||
like year, month, and day. Record-style classes override
|
||
<code>length()</code> and subsetting methods to conceal this
|
||
implementation detail.</p>
|
||
<div class="sourceCode" id="cb33"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb33-1"><a href="#cb33-1" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">as.POSIXlt</span>(<span class="fu">ISOdatetime</span>(<span class="dv">2020</span>, <span class="dv">1</span>, <span class="dv">1</span>, <span class="dv">0</span>, <span class="dv">0</span>, <span class="dv">1</span><span class="sc">:</span><span class="dv">3</span>))</span>
|
||
<span id="cb33-2"><a href="#cb33-2" tabindex="-1"></a>x</span>
|
||
<span id="cb33-3"><a href="#cb33-3" tabindex="-1"></a><span class="co">#> [1] "2020-01-01 00:00:01 EST" "2020-01-01 00:00:02 EST"</span></span>
|
||
<span id="cb33-4"><a href="#cb33-4" tabindex="-1"></a><span class="co">#> [3] "2020-01-01 00:00:03 EST"</span></span>
|
||
<span id="cb33-5"><a href="#cb33-5" tabindex="-1"></a></span>
|
||
<span id="cb33-6"><a href="#cb33-6" tabindex="-1"></a><span class="fu">length</span>(x)</span>
|
||
<span id="cb33-7"><a href="#cb33-7" tabindex="-1"></a><span class="co">#> [1] 3</span></span>
|
||
<span id="cb33-8"><a href="#cb33-8" tabindex="-1"></a><span class="fu">length</span>(<span class="fu">unclass</span>(x))</span>
|
||
<span id="cb33-9"><a href="#cb33-9" tabindex="-1"></a><span class="co">#> [1] 11</span></span>
|
||
<span id="cb33-10"><a href="#cb33-10" tabindex="-1"></a></span>
|
||
<span id="cb33-11"><a href="#cb33-11" tabindex="-1"></a>x[[<span class="dv">1</span>]] <span class="co"># the first date time</span></span>
|
||
<span id="cb33-12"><a href="#cb33-12" tabindex="-1"></a><span class="co">#> [1] "2020-01-01 00:00:01 EST"</span></span>
|
||
<span id="cb33-13"><a href="#cb33-13" tabindex="-1"></a><span class="fu">unclass</span>(x)[[<span class="dv">1</span>]] <span class="co"># the first component, the number of seconds</span></span>
|
||
<span id="cb33-14"><a href="#cb33-14" tabindex="-1"></a><span class="co">#> [1] 1 2 3</span></span></code></pre></div>
|
||
<p>vctrs makes it easy to create new record-style classes using
|
||
<code>new_rcrd()</code>, which has a wide selection of default
|
||
methods.</p>
|
||
<div id="rational-class" class="section level3">
|
||
<h3>Rational class</h3>
|
||
<p>A fraction, or rational number, can be represented by a pair of
|
||
integer vectors representing the numerator (the number on top) and the
|
||
denominator (the number on bottom), where the length of each vector must
|
||
be the same. To represent such a data structure we turn to a new base
|
||
data type: the record (or rcrd for short).</p>
|
||
<p>As usual we start with low-level and user-friendly constructors. The
|
||
low-level constructor calls <code>new_rcrd()</code>, which needs a named
|
||
list of equal-length vectors.</p>
|
||
<div class="sourceCode" id="cb34"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb34-1"><a href="#cb34-1" tabindex="-1"></a>new_rational <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">n =</span> <span class="fu">integer</span>(), <span class="at">d =</span> <span class="fu">integer</span>()) {</span>
|
||
<span id="cb34-2"><a href="#cb34-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_integer</span>(n)) {</span>
|
||
<span id="cb34-3"><a href="#cb34-3" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`n` must be an integer vector."</span>)</span>
|
||
<span id="cb34-4"><a href="#cb34-4" tabindex="-1"></a> }</span>
|
||
<span id="cb34-5"><a href="#cb34-5" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_integer</span>(d)) {</span>
|
||
<span id="cb34-6"><a href="#cb34-6" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`d` must be an integer vector."</span>)</span>
|
||
<span id="cb34-7"><a href="#cb34-7" tabindex="-1"></a> }</span>
|
||
<span id="cb34-8"><a href="#cb34-8" tabindex="-1"></a></span>
|
||
<span id="cb34-9"><a href="#cb34-9" tabindex="-1"></a> <span class="fu">new_rcrd</span>(<span class="fu">list</span>(<span class="at">n =</span> n, <span class="at">d =</span> d), <span class="at">class =</span> <span class="st">"vctrs_rational"</span>)</span>
|
||
<span id="cb34-10"><a href="#cb34-10" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Our user friendly constructor casts <code>n</code> and <code>d</code>
|
||
to integers and recycles them to the same length.</p>
|
||
<div class="sourceCode" id="cb35"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb35-1"><a href="#cb35-1" tabindex="-1"></a>rational <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">n =</span> <span class="fu">integer</span>(), <span class="at">d =</span> <span class="fu">integer</span>()) {</span>
|
||
<span id="cb35-2"><a href="#cb35-2" tabindex="-1"></a> <span class="fu">c</span>(n, d) <span class="sc">%<-%</span> <span class="fu">vec_cast_common</span>(n, d, <span class="at">.to =</span> <span class="fu">integer</span>())</span>
|
||
<span id="cb35-3"><a href="#cb35-3" tabindex="-1"></a> <span class="fu">c</span>(n, d) <span class="sc">%<-%</span> <span class="fu">vec_recycle_common</span>(n, d)</span>
|
||
<span id="cb35-4"><a href="#cb35-4" tabindex="-1"></a></span>
|
||
<span id="cb35-5"><a href="#cb35-5" tabindex="-1"></a> <span class="fu">new_rational</span>(n, d)</span>
|
||
<span id="cb35-6"><a href="#cb35-6" tabindex="-1"></a>}</span>
|
||
<span id="cb35-7"><a href="#cb35-7" tabindex="-1"></a></span>
|
||
<span id="cb35-8"><a href="#cb35-8" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">rational</span>(<span class="dv">1</span>, <span class="dv">1</span><span class="sc">:</span><span class="dv">10</span>)</span></code></pre></div>
|
||
<p>Behind the scenes, <code>x</code> is a named list with two elements.
|
||
But those details are hidden so that it behaves like a vector:</p>
|
||
<div class="sourceCode" id="cb36"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb36-1"><a href="#cb36-1" tabindex="-1"></a><span class="fu">names</span>(x)</span>
|
||
<span id="cb36-2"><a href="#cb36-2" tabindex="-1"></a><span class="co">#> NULL</span></span>
|
||
<span id="cb36-3"><a href="#cb36-3" tabindex="-1"></a><span class="fu">length</span>(x)</span>
|
||
<span id="cb36-4"><a href="#cb36-4" tabindex="-1"></a><span class="co">#> [1] 10</span></span></code></pre></div>
|
||
<p>To access the underlying fields we need to use <code>field()</code>
|
||
and <code>fields()</code>:</p>
|
||
<div class="sourceCode" id="cb37"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb37-1"><a href="#cb37-1" tabindex="-1"></a><span class="fu">fields</span>(x)</span>
|
||
<span id="cb37-2"><a href="#cb37-2" tabindex="-1"></a><span class="co">#> [1] "n" "d"</span></span>
|
||
<span id="cb37-3"><a href="#cb37-3" tabindex="-1"></a><span class="fu">field</span>(x, <span class="st">"n"</span>)</span>
|
||
<span id="cb37-4"><a href="#cb37-4" tabindex="-1"></a><span class="co">#> [1] 1 1 1 1 1 1 1 1 1 1</span></span></code></pre></div>
|
||
<p>Notice that we can’t <code>print()</code> or <code>str()</code> the
|
||
new rational vector <code>x</code> yet. Printing causes an error:</p>
|
||
<div class="sourceCode" id="cb38"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb38-1"><a href="#cb38-1" tabindex="-1"></a>x</span>
|
||
<span id="cb38-2"><a href="#cb38-2" tabindex="-1"></a><span class="co">#> <vctrs_rational[10]></span></span>
|
||
<span id="cb38-3"><a href="#cb38-3" tabindex="-1"></a><span class="co">#> Error in `format()`:</span></span>
|
||
<span id="cb38-4"><a href="#cb38-4" tabindex="-1"></a><span class="co">#> ! `format.vctrs_rational()` not implemented.</span></span>
|
||
<span id="cb38-5"><a href="#cb38-5" tabindex="-1"></a></span>
|
||
<span id="cb38-6"><a href="#cb38-6" tabindex="-1"></a><span class="fu">str</span>(x)</span>
|
||
<span id="cb38-7"><a href="#cb38-7" tabindex="-1"></a><span class="co">#> Error in `format()`:</span></span>
|
||
<span id="cb38-8"><a href="#cb38-8" tabindex="-1"></a><span class="co">#> ! `format.vctrs_rational()` not implemented.</span></span></code></pre></div>
|
||
<p>This is because we haven’t defined how our class can be printed from
|
||
the underlying data. Note that if you want to look under the hood during
|
||
development, you can always call <code>vec_data(x)</code>.</p>
|
||
<div class="sourceCode" id="cb39"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb39-1"><a href="#cb39-1" tabindex="-1"></a><span class="fu">vec_data</span>(x)</span>
|
||
<span id="cb39-2"><a href="#cb39-2" tabindex="-1"></a><span class="co">#> n d</span></span>
|
||
<span id="cb39-3"><a href="#cb39-3" tabindex="-1"></a><span class="co">#> 1 1 1</span></span>
|
||
<span id="cb39-4"><a href="#cb39-4" tabindex="-1"></a><span class="co">#> 2 1 2</span></span>
|
||
<span id="cb39-5"><a href="#cb39-5" tabindex="-1"></a><span class="co">#> 3 1 3</span></span>
|
||
<span id="cb39-6"><a href="#cb39-6" tabindex="-1"></a><span class="co">#> 4 1 4</span></span>
|
||
<span id="cb39-7"><a href="#cb39-7" tabindex="-1"></a><span class="co">#> 5 1 5</span></span>
|
||
<span id="cb39-8"><a href="#cb39-8" tabindex="-1"></a><span class="co">#> 6 1 6</span></span>
|
||
<span id="cb39-9"><a href="#cb39-9" tabindex="-1"></a><span class="co">#> 7 1 7</span></span>
|
||
<span id="cb39-10"><a href="#cb39-10" tabindex="-1"></a><span class="co">#> 8 1 8</span></span>
|
||
<span id="cb39-11"><a href="#cb39-11" tabindex="-1"></a><span class="co">#> 9 1 9</span></span>
|
||
<span id="cb39-12"><a href="#cb39-12" tabindex="-1"></a><span class="co">#> 10 1 10</span></span>
|
||
<span id="cb39-13"><a href="#cb39-13" tabindex="-1"></a></span>
|
||
<span id="cb39-14"><a href="#cb39-14" tabindex="-1"></a><span class="fu">str</span>(<span class="fu">vec_data</span>(x))</span>
|
||
<span id="cb39-15"><a href="#cb39-15" tabindex="-1"></a><span class="co">#> 'data.frame': 10 obs. of 2 variables:</span></span>
|
||
<span id="cb39-16"><a href="#cb39-16" tabindex="-1"></a><span class="co">#> $ n: int 1 1 1 1 1 1 1 1 1 1</span></span>
|
||
<span id="cb39-17"><a href="#cb39-17" tabindex="-1"></a><span class="co">#> $ d: int 1 2 3 4 5 6 7 8 9 10</span></span></code></pre></div>
|
||
<p>It is generally best to define a formatting method early in the
|
||
development of a class. The format method defines how to display the
|
||
class so that it can be printed in the normal way:</p>
|
||
<div class="sourceCode" id="cb40"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb40-1"><a href="#cb40-1" tabindex="-1"></a>format.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb40-2"><a href="#cb40-2" tabindex="-1"></a> n <span class="ot"><-</span> <span class="fu">field</span>(x, <span class="st">"n"</span>)</span>
|
||
<span id="cb40-3"><a href="#cb40-3" tabindex="-1"></a> d <span class="ot"><-</span> <span class="fu">field</span>(x, <span class="st">"d"</span>)</span>
|
||
<span id="cb40-4"><a href="#cb40-4" tabindex="-1"></a></span>
|
||
<span id="cb40-5"><a href="#cb40-5" tabindex="-1"></a> out <span class="ot"><-</span> <span class="fu">paste0</span>(n, <span class="st">"/"</span>, d)</span>
|
||
<span id="cb40-6"><a href="#cb40-6" tabindex="-1"></a> out[<span class="fu">is.na</span>(n) <span class="sc">|</span> <span class="fu">is.na</span>(d)] <span class="ot"><-</span> <span class="cn">NA</span></span>
|
||
<span id="cb40-7"><a href="#cb40-7" tabindex="-1"></a></span>
|
||
<span id="cb40-8"><a href="#cb40-8" tabindex="-1"></a> out</span>
|
||
<span id="cb40-9"><a href="#cb40-9" tabindex="-1"></a>}</span>
|
||
<span id="cb40-10"><a href="#cb40-10" tabindex="-1"></a></span>
|
||
<span id="cb40-11"><a href="#cb40-11" tabindex="-1"></a>vec_ptype_abbr.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, ...) <span class="st">"rtnl"</span></span>
|
||
<span id="cb40-12"><a href="#cb40-12" tabindex="-1"></a>vec_ptype_full.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, ...) <span class="st">"rational"</span></span>
|
||
<span id="cb40-13"><a href="#cb40-13" tabindex="-1"></a></span>
|
||
<span id="cb40-14"><a href="#cb40-14" tabindex="-1"></a>x</span>
|
||
<span id="cb40-15"><a href="#cb40-15" tabindex="-1"></a><span class="co">#> <rational[10]></span></span>
|
||
<span id="cb40-16"><a href="#cb40-16" tabindex="-1"></a><span class="co">#> [1] 1/1 1/2 1/3 1/4 1/5 1/6 1/7 1/8 1/9 1/10</span></span></code></pre></div>
|
||
<p>vctrs uses the <code>format()</code> method in <code>str()</code>,
|
||
hiding the underlying implementation details from the user:</p>
|
||
<div class="sourceCode" id="cb41"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb41-1"><a href="#cb41-1" tabindex="-1"></a><span class="fu">str</span>(x)</span>
|
||
<span id="cb41-2"><a href="#cb41-2" tabindex="-1"></a><span class="co">#> rtnl [1:10] 1/1, 1/2, 1/3, 1/4, 1/5, 1/6, 1/7, 1/8, 1/9, 1/10</span></span></code></pre></div>
|
||
<p>For <code>rational</code>, <code>vec_ptype2()</code> and
|
||
<code>vec_cast()</code> follow the same pattern as
|
||
<code>percent()</code>. We allow coercion from integer and to
|
||
doubles.</p>
|
||
<div class="sourceCode" id="cb42"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb42-1"><a href="#cb42-1" tabindex="-1"></a>vec_ptype2.vctrs_rational.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">new_rational</span>()</span>
|
||
<span id="cb42-2"><a href="#cb42-2" tabindex="-1"></a>vec_ptype2.vctrs_rational.integer <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">new_rational</span>()</span>
|
||
<span id="cb42-3"><a href="#cb42-3" tabindex="-1"></a>vec_ptype2.integer.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">new_rational</span>()</span>
|
||
<span id="cb42-4"><a href="#cb42-4" tabindex="-1"></a></span>
|
||
<span id="cb42-5"><a href="#cb42-5" tabindex="-1"></a>vec_cast.vctrs_rational.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) x</span>
|
||
<span id="cb42-6"><a href="#cb42-6" tabindex="-1"></a>vec_cast.double.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">field</span>(x, <span class="st">"n"</span>) <span class="sc">/</span> <span class="fu">field</span>(x, <span class="st">"d"</span>)</span>
|
||
<span id="cb42-7"><a href="#cb42-7" tabindex="-1"></a>vec_cast.vctrs_rational.integer <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">rational</span>(x, <span class="dv">1</span>)</span>
|
||
<span id="cb42-8"><a href="#cb42-8" tabindex="-1"></a></span>
|
||
<span id="cb42-9"><a href="#cb42-9" tabindex="-1"></a><span class="fu">vec_c</span>(<span class="fu">rational</span>(<span class="dv">1</span>, <span class="dv">2</span>), <span class="dv">1</span><span class="dt">L</span>, <span class="cn">NA</span>)</span>
|
||
<span id="cb42-10"><a href="#cb42-10" tabindex="-1"></a><span class="co">#> <rational[3]></span></span>
|
||
<span id="cb42-11"><a href="#cb42-11" tabindex="-1"></a><span class="co">#> [1] 1/2 1/1 <NA></span></span></code></pre></div>
|
||
</div>
|
||
<div id="decimal2-class" class="section level3">
|
||
<h3>Decimal2 class</h3>
|
||
<p>The previous implementation of <code>decimal</code> was built on top
|
||
of doubles. This is a bad idea because decimal vectors are typically
|
||
used when you care about precise values (i.e., dollars and cents in a
|
||
bank account), and double values suffer from floating point
|
||
problems.</p>
|
||
<p>A better implementation of a decimal class would be to use pair of
|
||
integers, one for the value to the left of the decimal point, and the
|
||
other for the value to the right (divided by a <code>scale</code>). The
|
||
following code is a very quick sketch of how you might start creating
|
||
such a class:</p>
|
||
<div class="sourceCode" id="cb43"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb43-1"><a href="#cb43-1" tabindex="-1"></a>new_decimal2 <span class="ot"><-</span> <span class="cf">function</span>(l, r, <span class="at">scale =</span> <span class="dv">2</span><span class="dt">L</span>) {</span>
|
||
<span id="cb43-2"><a href="#cb43-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_integer</span>(l)) {</span>
|
||
<span id="cb43-3"><a href="#cb43-3" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`l` must be an integer vector."</span>)</span>
|
||
<span id="cb43-4"><a href="#cb43-4" tabindex="-1"></a> }</span>
|
||
<span id="cb43-5"><a href="#cb43-5" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_integer</span>(r)) {</span>
|
||
<span id="cb43-6"><a href="#cb43-6" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`r` must be an integer vector."</span>)</span>
|
||
<span id="cb43-7"><a href="#cb43-7" tabindex="-1"></a> }</span>
|
||
<span id="cb43-8"><a href="#cb43-8" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_integer</span>(scale)) {</span>
|
||
<span id="cb43-9"><a href="#cb43-9" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`scale` must be an integer vector."</span>)</span>
|
||
<span id="cb43-10"><a href="#cb43-10" tabindex="-1"></a> }</span>
|
||
<span id="cb43-11"><a href="#cb43-11" tabindex="-1"></a> <span class="fu">vec_check_size</span>(scale, <span class="at">size =</span> <span class="dv">1</span><span class="dt">L</span>)</span>
|
||
<span id="cb43-12"><a href="#cb43-12" tabindex="-1"></a></span>
|
||
<span id="cb43-13"><a href="#cb43-13" tabindex="-1"></a> <span class="fu">new_rcrd</span>(<span class="fu">list</span>(<span class="at">l =</span> l, <span class="at">r =</span> r), <span class="at">scale =</span> scale, <span class="at">class =</span> <span class="st">"vctrs_decimal2"</span>)</span>
|
||
<span id="cb43-14"><a href="#cb43-14" tabindex="-1"></a>}</span>
|
||
<span id="cb43-15"><a href="#cb43-15" tabindex="-1"></a></span>
|
||
<span id="cb43-16"><a href="#cb43-16" tabindex="-1"></a>decimal2 <span class="ot"><-</span> <span class="cf">function</span>(l, r, <span class="at">scale =</span> <span class="dv">2</span><span class="dt">L</span>) {</span>
|
||
<span id="cb43-17"><a href="#cb43-17" tabindex="-1"></a> l <span class="ot"><-</span> <span class="fu">vec_cast</span>(l, <span class="fu">integer</span>())</span>
|
||
<span id="cb43-18"><a href="#cb43-18" tabindex="-1"></a> r <span class="ot"><-</span> <span class="fu">vec_cast</span>(r, <span class="fu">integer</span>())</span>
|
||
<span id="cb43-19"><a href="#cb43-19" tabindex="-1"></a> <span class="fu">c</span>(l, r) <span class="sc">%<-%</span> <span class="fu">vec_recycle_common</span>(l, r)</span>
|
||
<span id="cb43-20"><a href="#cb43-20" tabindex="-1"></a> scale <span class="ot"><-</span> <span class="fu">vec_cast</span>(scale, <span class="fu">integer</span>())</span>
|
||
<span id="cb43-21"><a href="#cb43-21" tabindex="-1"></a></span>
|
||
<span id="cb43-22"><a href="#cb43-22" tabindex="-1"></a> <span class="co"># should check that r < 10^scale</span></span>
|
||
<span id="cb43-23"><a href="#cb43-23" tabindex="-1"></a> <span class="fu">new_decimal2</span>(<span class="at">l =</span> l, <span class="at">r =</span> r, <span class="at">scale =</span> scale)</span>
|
||
<span id="cb43-24"><a href="#cb43-24" tabindex="-1"></a>}</span>
|
||
<span id="cb43-25"><a href="#cb43-25" tabindex="-1"></a></span>
|
||
<span id="cb43-26"><a href="#cb43-26" tabindex="-1"></a>format.vctrs_decimal2 <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb43-27"><a href="#cb43-27" tabindex="-1"></a> val <span class="ot"><-</span> <span class="fu">field</span>(x, <span class="st">"l"</span>) <span class="sc">+</span> <span class="fu">field</span>(x, <span class="st">"r"</span>) <span class="sc">/</span> <span class="dv">10</span><span class="sc">^</span><span class="fu">attr</span>(x, <span class="st">"scale"</span>)</span>
|
||
<span id="cb43-28"><a href="#cb43-28" tabindex="-1"></a> <span class="fu">sprintf</span>(<span class="fu">paste0</span>(<span class="st">"%.0"</span>, <span class="fu">attr</span>(x, <span class="st">"scale"</span>), <span class="st">"f"</span>), val)</span>
|
||
<span id="cb43-29"><a href="#cb43-29" tabindex="-1"></a>}</span>
|
||
<span id="cb43-30"><a href="#cb43-30" tabindex="-1"></a></span>
|
||
<span id="cb43-31"><a href="#cb43-31" tabindex="-1"></a><span class="fu">decimal2</span>(<span class="dv">10</span>, <span class="fu">c</span>(<span class="dv">0</span>, <span class="dv">5</span>, <span class="dv">99</span>))</span>
|
||
<span id="cb43-32"><a href="#cb43-32" tabindex="-1"></a><span class="co">#> <vctrs_decimal2[3]></span></span>
|
||
<span id="cb43-33"><a href="#cb43-33" tabindex="-1"></a><span class="co">#> [1] 10.00 10.05 10.99</span></span></code></pre></div>
|
||
</div>
|
||
</div>
|
||
<div id="equality-and-comparison" class="section level2">
|
||
<h2>Equality and comparison</h2>
|
||
<p>vctrs provides four “proxy” generics. Two of these let you control
|
||
how your class determines equality and comparison:</p>
|
||
<ul>
|
||
<li><p><code>vec_proxy_equal()</code> returns a data vector suitable for
|
||
comparison. It underpins <code>==</code>, <code>!=</code>,
|
||
<code>unique()</code>, <code>anyDuplicated()</code>, and
|
||
<code>is.na()</code>.</p></li>
|
||
<li><p><code>vec_proxy_compare()</code> specifies how to compare the
|
||
elements of your vector. This proxy is used in <code><</code>,
|
||
<code><=</code>, <code>>=</code>, <code>></code>,
|
||
<code>min()</code>, <code>max()</code>, <code>median()</code>, and
|
||
<code>quantile()</code>.</p></li>
|
||
</ul>
|
||
<p>Two other proxy generic are used for sorting for unordered data types
|
||
and for accessing the raw data for exotic storage formats:</p>
|
||
<ul>
|
||
<li><p><code>vec_proxy_order()</code> specifies how to sort the elements
|
||
of your vector. It is used in <code>xtfrm()</code>, which in turn is
|
||
called by the <code>order()</code> and <code>sort()</code>
|
||
functions.</p>
|
||
<p>This proxy was added to implement the behaviour of lists, which are
|
||
sortable (their order proxy sorts by first occurrence) but not
|
||
comparable (comparison operators cause an error). Its default
|
||
implementation for other classes calls <code>vec_proxy_compare()</code>
|
||
and you normally don’t need to implement this proxy.</p></li>
|
||
<li><p><code>vec_proxy()</code> returns the actual data of a vector.
|
||
This is useful when you store the data in a field of your class. Most of
|
||
the time, you shouldn’t need to implement
|
||
<code>vec_proxy()</code>.</p></li>
|
||
</ul>
|
||
<p>The default behavior is as follows:</p>
|
||
<ul>
|
||
<li><code>vec_proxy_equal()</code> calls <code>vec_proxy()</code></li>
|
||
<li><code>vec_proxy_compare()</code> calls
|
||
<code>vec_proxy_equal()</code></li>
|
||
<li><code>vec_proxy_order()</code> calls
|
||
<code>vec_proxy_compare()</code></li>
|
||
</ul>
|
||
<p>You should only implement these proxies when some preprocessing on
|
||
the data is needed to make elements comparable. In that case, defining
|
||
these methods will get you a lot of behaviour for relatively little
|
||
work.</p>
|
||
<p>These proxy functions should always return a simple object (either a
|
||
bare vector or a data frame) that possesses the same properties as your
|
||
class. This permits efficient implementation of the vctrs internals
|
||
because it allows dispatch to happen once in R, and then efficient
|
||
computations can be written in C.</p>
|
||
<div id="rational-class-1" class="section level3">
|
||
<h3>Rational class</h3>
|
||
<p>Let’s explore these ideas by with the rational class we started on
|
||
above. By default, <code>vec_proxy()</code> converts a record to a data
|
||
frame, and the default comparison works column by column:</p>
|
||
<div class="sourceCode" id="cb44"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb44-1"><a href="#cb44-1" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">rational</span>(<span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">1</span>, <span class="dv">2</span>), <span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">1</span>, <span class="dv">2</span>, <span class="dv">2</span>))</span>
|
||
<span id="cb44-2"><a href="#cb44-2" tabindex="-1"></a>x</span>
|
||
<span id="cb44-3"><a href="#cb44-3" tabindex="-1"></a><span class="co">#> <rational[4]></span></span>
|
||
<span id="cb44-4"><a href="#cb44-4" tabindex="-1"></a><span class="co">#> [1] 1/1 2/1 1/2 2/2</span></span>
|
||
<span id="cb44-5"><a href="#cb44-5" tabindex="-1"></a></span>
|
||
<span id="cb44-6"><a href="#cb44-6" tabindex="-1"></a><span class="fu">vec_proxy</span>(x)</span>
|
||
<span id="cb44-7"><a href="#cb44-7" tabindex="-1"></a><span class="co">#> n d</span></span>
|
||
<span id="cb44-8"><a href="#cb44-8" tabindex="-1"></a><span class="co">#> 1 1 1</span></span>
|
||
<span id="cb44-9"><a href="#cb44-9" tabindex="-1"></a><span class="co">#> 2 2 1</span></span>
|
||
<span id="cb44-10"><a href="#cb44-10" tabindex="-1"></a><span class="co">#> 3 1 2</span></span>
|
||
<span id="cb44-11"><a href="#cb44-11" tabindex="-1"></a><span class="co">#> 4 2 2</span></span>
|
||
<span id="cb44-12"><a href="#cb44-12" tabindex="-1"></a></span>
|
||
<span id="cb44-13"><a href="#cb44-13" tabindex="-1"></a>x <span class="sc">==</span> <span class="fu">rational</span>(<span class="dv">1</span>, <span class="dv">1</span>)</span>
|
||
<span id="cb44-14"><a href="#cb44-14" tabindex="-1"></a><span class="co">#> [1] TRUE FALSE FALSE FALSE</span></span></code></pre></div>
|
||
<p>This makes sense as a default but isn’t correct here because
|
||
<code>rational(1, 1)</code> represents the same number as
|
||
<code>rational(2, 2)</code>, so they should be equal. We can fix that by
|
||
implementing a <code>vec_proxy_equal()</code> method that divides
|
||
<code>n</code> and <code>d</code> by their greatest common divisor:</p>
|
||
<div class="sourceCode" id="cb45"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb45-1"><a href="#cb45-1" tabindex="-1"></a><span class="co"># Thanks to Matthew Lundberg: https://stackoverflow.com/a/21504113/16632</span></span>
|
||
<span id="cb45-2"><a href="#cb45-2" tabindex="-1"></a>gcd <span class="ot"><-</span> <span class="cf">function</span>(x, y) {</span>
|
||
<span id="cb45-3"><a href="#cb45-3" tabindex="-1"></a> r <span class="ot"><-</span> x <span class="sc">%%</span> y</span>
|
||
<span id="cb45-4"><a href="#cb45-4" tabindex="-1"></a> <span class="fu">ifelse</span>(r, <span class="fu">gcd</span>(y, r), y)</span>
|
||
<span id="cb45-5"><a href="#cb45-5" tabindex="-1"></a>}</span>
|
||
<span id="cb45-6"><a href="#cb45-6" tabindex="-1"></a></span>
|
||
<span id="cb45-7"><a href="#cb45-7" tabindex="-1"></a>vec_proxy_equal.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb45-8"><a href="#cb45-8" tabindex="-1"></a> n <span class="ot"><-</span> <span class="fu">field</span>(x, <span class="st">"n"</span>)</span>
|
||
<span id="cb45-9"><a href="#cb45-9" tabindex="-1"></a> d <span class="ot"><-</span> <span class="fu">field</span>(x, <span class="st">"d"</span>)</span>
|
||
<span id="cb45-10"><a href="#cb45-10" tabindex="-1"></a> gcd <span class="ot"><-</span> <span class="fu">gcd</span>(n, d)</span>
|
||
<span id="cb45-11"><a href="#cb45-11" tabindex="-1"></a></span>
|
||
<span id="cb45-12"><a href="#cb45-12" tabindex="-1"></a> <span class="fu">data.frame</span>(<span class="at">n =</span> n <span class="sc">/</span> gcd, <span class="at">d =</span> d <span class="sc">/</span> gcd)</span>
|
||
<span id="cb45-13"><a href="#cb45-13" tabindex="-1"></a>}</span>
|
||
<span id="cb45-14"><a href="#cb45-14" tabindex="-1"></a><span class="fu">vec_proxy_equal</span>(x)</span>
|
||
<span id="cb45-15"><a href="#cb45-15" tabindex="-1"></a><span class="co">#> n d</span></span>
|
||
<span id="cb45-16"><a href="#cb45-16" tabindex="-1"></a><span class="co">#> 1 1 1</span></span>
|
||
<span id="cb45-17"><a href="#cb45-17" tabindex="-1"></a><span class="co">#> 2 2 1</span></span>
|
||
<span id="cb45-18"><a href="#cb45-18" tabindex="-1"></a><span class="co">#> 3 1 2</span></span>
|
||
<span id="cb45-19"><a href="#cb45-19" tabindex="-1"></a><span class="co">#> 4 1 1</span></span>
|
||
<span id="cb45-20"><a href="#cb45-20" tabindex="-1"></a></span>
|
||
<span id="cb45-21"><a href="#cb45-21" tabindex="-1"></a>x <span class="sc">==</span> <span class="fu">rational</span>(<span class="dv">1</span>, <span class="dv">1</span>)</span>
|
||
<span id="cb45-22"><a href="#cb45-22" tabindex="-1"></a><span class="co">#> [1] TRUE FALSE FALSE TRUE</span></span></code></pre></div>
|
||
<p><code>vec_proxy_equal()</code> is also used by
|
||
<code>unique()</code>:</p>
|
||
<div class="sourceCode" id="cb46"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb46-1"><a href="#cb46-1" tabindex="-1"></a><span class="fu">unique</span>(x)</span>
|
||
<span id="cb46-2"><a href="#cb46-2" tabindex="-1"></a><span class="co">#> <rational[3]></span></span>
|
||
<span id="cb46-3"><a href="#cb46-3" tabindex="-1"></a><span class="co">#> [1] 1/1 2/1 1/2</span></span></code></pre></div>
|
||
<p>We now need to fix the comparison operations similarly, since
|
||
comparison currently happens lexicographically by <code>n</code>, then
|
||
by <code>d</code>:</p>
|
||
<div class="sourceCode" id="cb47"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb47-1"><a href="#cb47-1" tabindex="-1"></a><span class="fu">rational</span>(<span class="dv">1</span>, <span class="dv">2</span>) <span class="sc"><</span> <span class="fu">rational</span>(<span class="dv">2</span>, <span class="dv">3</span>)</span>
|
||
<span id="cb47-2"><a href="#cb47-2" tabindex="-1"></a><span class="co">#> [1] TRUE</span></span>
|
||
<span id="cb47-3"><a href="#cb47-3" tabindex="-1"></a><span class="fu">rational</span>(<span class="dv">2</span>, <span class="dv">4</span>) <span class="sc"><</span> <span class="fu">rational</span>(<span class="dv">2</span>, <span class="dv">3</span>)</span>
|
||
<span id="cb47-4"><a href="#cb47-4" tabindex="-1"></a><span class="co">#> [1] TRUE</span></span></code></pre></div>
|
||
<p>The easiest fix is to convert the fraction to a floating point number
|
||
and use this as a proxy:</p>
|
||
<div class="sourceCode" id="cb48"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb48-1"><a href="#cb48-1" tabindex="-1"></a>vec_proxy_compare.vctrs_rational <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb48-2"><a href="#cb48-2" tabindex="-1"></a> <span class="fu">field</span>(x, <span class="st">"n"</span>) <span class="sc">/</span> <span class="fu">field</span>(x, <span class="st">"d"</span>)</span>
|
||
<span id="cb48-3"><a href="#cb48-3" tabindex="-1"></a>}</span>
|
||
<span id="cb48-4"><a href="#cb48-4" tabindex="-1"></a></span>
|
||
<span id="cb48-5"><a href="#cb48-5" tabindex="-1"></a><span class="fu">rational</span>(<span class="dv">2</span>, <span class="dv">4</span>) <span class="sc"><</span> <span class="fu">rational</span>(<span class="dv">2</span>, <span class="dv">3</span>)</span>
|
||
<span id="cb48-6"><a href="#cb48-6" tabindex="-1"></a><span class="co">#> [1] TRUE</span></span></code></pre></div>
|
||
<p>This also fixes <code>sort()</code>, because the default
|
||
implementation of <code>vec_proxy_order()</code> calls
|
||
<code>vec_proxy_compare()</code>.</p>
|
||
<div class="sourceCode" id="cb49"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb49-1"><a href="#cb49-1" tabindex="-1"></a><span class="fu">sort</span>(x)</span>
|
||
<span id="cb49-2"><a href="#cb49-2" tabindex="-1"></a><span class="co">#> <rational[4]></span></span>
|
||
<span id="cb49-3"><a href="#cb49-3" tabindex="-1"></a><span class="co">#> [1] 1/2 1/1 2/2 2/1</span></span></code></pre></div>
|
||
<p>(We could have used the same approach in
|
||
<code>vec_proxy_equal()</code>, but when working with floating point
|
||
numbers it’s not necessarily true that <code>x == y</code> implies that
|
||
<code>d * x == d * y</code>.)</p>
|
||
</div>
|
||
<div id="polynomial-class" class="section level3">
|
||
<h3>Polynomial class</h3>
|
||
<p>A related problem occurs if we build our vector on top of a list. The
|
||
following code defines a polynomial class that represents polynomials
|
||
(like <code>1 + 3x - 2x^2</code>) using a list of integer vectors (like
|
||
<code>c(1, 3, -2)</code>). Note the use of <code>new_list_of()</code> in
|
||
the constructor.</p>
|
||
<div class="sourceCode" id="cb50"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb50-1"><a href="#cb50-1" tabindex="-1"></a>poly <span class="ot"><-</span> <span class="cf">function</span>(...) {</span>
|
||
<span id="cb50-2"><a href="#cb50-2" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast_common</span>(..., <span class="at">.to =</span> <span class="fu">integer</span>())</span>
|
||
<span id="cb50-3"><a href="#cb50-3" tabindex="-1"></a> <span class="fu">new_poly</span>(x)</span>
|
||
<span id="cb50-4"><a href="#cb50-4" tabindex="-1"></a>}</span>
|
||
<span id="cb50-5"><a href="#cb50-5" tabindex="-1"></a>new_poly <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb50-6"><a href="#cb50-6" tabindex="-1"></a> <span class="fu">new_list_of</span>(x, <span class="at">ptype =</span> <span class="fu">integer</span>(), <span class="at">class =</span> <span class="st">"vctrs_poly_list"</span>)</span>
|
||
<span id="cb50-7"><a href="#cb50-7" tabindex="-1"></a>}</span>
|
||
<span id="cb50-8"><a href="#cb50-8" tabindex="-1"></a></span>
|
||
<span id="cb50-9"><a href="#cb50-9" tabindex="-1"></a>vec_ptype_full.vctrs_poly_list <span class="ot"><-</span> <span class="cf">function</span>(x, ...) <span class="st">"polynomial"</span></span>
|
||
<span id="cb50-10"><a href="#cb50-10" tabindex="-1"></a>vec_ptype_abbr.vctrs_poly_list <span class="ot"><-</span> <span class="cf">function</span>(x, ...) <span class="st">"poly"</span></span>
|
||
<span id="cb50-11"><a href="#cb50-11" tabindex="-1"></a></span>
|
||
<span id="cb50-12"><a href="#cb50-12" tabindex="-1"></a>format.vctrs_poly_list <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb50-13"><a href="#cb50-13" tabindex="-1"></a> format_one <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb50-14"><a href="#cb50-14" tabindex="-1"></a> <span class="cf">if</span> (<span class="fu">length</span>(x) <span class="sc">==</span> <span class="dv">0</span>) {</span>
|
||
<span id="cb50-15"><a href="#cb50-15" tabindex="-1"></a> <span class="fu">return</span>(<span class="st">""</span>)</span>
|
||
<span id="cb50-16"><a href="#cb50-16" tabindex="-1"></a> }</span>
|
||
<span id="cb50-17"><a href="#cb50-17" tabindex="-1"></a></span>
|
||
<span id="cb50-18"><a href="#cb50-18" tabindex="-1"></a> <span class="cf">if</span> (<span class="fu">length</span>(x) <span class="sc">==</span> <span class="dv">1</span>) {</span>
|
||
<span id="cb50-19"><a href="#cb50-19" tabindex="-1"></a> <span class="fu">format</span>(x)</span>
|
||
<span id="cb50-20"><a href="#cb50-20" tabindex="-1"></a> } <span class="cf">else</span> {</span>
|
||
<span id="cb50-21"><a href="#cb50-21" tabindex="-1"></a> suffix <span class="ot"><-</span> <span class="fu">c</span>(<span class="fu">paste0</span>(<span class="st">"\u22C5x^"</span>, <span class="fu">seq</span>(<span class="fu">length</span>(x) <span class="sc">-</span> <span class="dv">1</span>, <span class="dv">1</span>)), <span class="st">""</span>)</span>
|
||
<span id="cb50-22"><a href="#cb50-22" tabindex="-1"></a> out <span class="ot"><-</span> <span class="fu">paste0</span>(x, suffix)</span>
|
||
<span id="cb50-23"><a href="#cb50-23" tabindex="-1"></a> out <span class="ot"><-</span> out[x <span class="sc">!=</span> <span class="dv">0</span><span class="dt">L</span>]</span>
|
||
<span id="cb50-24"><a href="#cb50-24" tabindex="-1"></a> <span class="fu">paste0</span>(out, <span class="at">collapse =</span> <span class="st">" + "</span>)</span>
|
||
<span id="cb50-25"><a href="#cb50-25" tabindex="-1"></a> }</span>
|
||
<span id="cb50-26"><a href="#cb50-26" tabindex="-1"></a> }</span>
|
||
<span id="cb50-27"><a href="#cb50-27" tabindex="-1"></a></span>
|
||
<span id="cb50-28"><a href="#cb50-28" tabindex="-1"></a> <span class="fu">vapply</span>(x, format_one, <span class="fu">character</span>(<span class="dv">1</span>))</span>
|
||
<span id="cb50-29"><a href="#cb50-29" tabindex="-1"></a>}</span>
|
||
<span id="cb50-30"><a href="#cb50-30" tabindex="-1"></a></span>
|
||
<span id="cb50-31"><a href="#cb50-31" tabindex="-1"></a>obj_print_data.vctrs_poly_list <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb50-32"><a href="#cb50-32" tabindex="-1"></a> <span class="cf">if</span> (<span class="fu">length</span>(x) <span class="sc">!=</span> <span class="dv">0</span>) {</span>
|
||
<span id="cb50-33"><a href="#cb50-33" tabindex="-1"></a> <span class="fu">print</span>(<span class="fu">format</span>(x), <span class="at">quote =</span> <span class="cn">FALSE</span>)</span>
|
||
<span id="cb50-34"><a href="#cb50-34" tabindex="-1"></a> }</span>
|
||
<span id="cb50-35"><a href="#cb50-35" tabindex="-1"></a>}</span>
|
||
<span id="cb50-36"><a href="#cb50-36" tabindex="-1"></a></span>
|
||
<span id="cb50-37"><a href="#cb50-37" tabindex="-1"></a>p <span class="ot"><-</span> <span class="fu">poly</span>(<span class="dv">1</span>, <span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">0</span>, <span class="dv">0</span>, <span class="dv">0</span>, <span class="dv">2</span>), <span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">0</span>, <span class="dv">1</span>))</span>
|
||
<span id="cb50-38"><a href="#cb50-38" tabindex="-1"></a>p</span>
|
||
<span id="cb50-39"><a href="#cb50-39" tabindex="-1"></a><span class="co">#> <polynomial[3]></span></span>
|
||
<span id="cb50-40"><a href="#cb50-40" tabindex="-1"></a><span class="co">#> [1] 1 1⋅x^4 + 2 1⋅x^2 + 1</span></span></code></pre></div>
|
||
<p>The resulting objects will inherit from the
|
||
<code>vctrs_list_of</code> class, which provides tailored methods for
|
||
<code>$</code>, <code>[[</code>, the corresponding assignment operators,
|
||
and other methods.</p>
|
||
<div class="sourceCode" id="cb51"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb51-1"><a href="#cb51-1" tabindex="-1"></a><span class="fu">class</span>(p)</span>
|
||
<span id="cb51-2"><a href="#cb51-2" tabindex="-1"></a><span class="co">#> [1] "vctrs_poly_list" "vctrs_list_of" "vctrs_vctr" "list"</span></span>
|
||
<span id="cb51-3"><a href="#cb51-3" tabindex="-1"></a>p[<span class="dv">2</span>]</span>
|
||
<span id="cb51-4"><a href="#cb51-4" tabindex="-1"></a><span class="co">#> <polynomial[1]></span></span>
|
||
<span id="cb51-5"><a href="#cb51-5" tabindex="-1"></a><span class="co">#> [1] 1⋅x^4 + 2</span></span>
|
||
<span id="cb51-6"><a href="#cb51-6" tabindex="-1"></a>p[[<span class="dv">2</span>]]</span>
|
||
<span id="cb51-7"><a href="#cb51-7" tabindex="-1"></a><span class="co">#> [1] 1 0 0 0 2</span></span></code></pre></div>
|
||
<p>The class implements the list interface:</p>
|
||
<div class="sourceCode" id="cb52"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb52-1"><a href="#cb52-1" tabindex="-1"></a><span class="fu">obj_is_list</span>(p)</span>
|
||
<span id="cb52-2"><a href="#cb52-2" tabindex="-1"></a><span class="co">#> [1] TRUE</span></span></code></pre></div>
|
||
<p>This is fine for the internal implementation of this class but it
|
||
would be more appropriate if it behaved like an atomic vector rather
|
||
than a list.</p>
|
||
<div id="make-an-atomic-polynomial-vector" class="section level4">
|
||
<h4>Make an atomic polynomial vector</h4>
|
||
<p>An atomic vector is a vector like integer or character for which
|
||
<code>[[</code> returns the same type. Unlike lists, you can’t reach
|
||
inside an atomic vector.</p>
|
||
<p>To make the polynomial class an atomic vector, we’ll wrap the
|
||
internal <code>list_of()</code> class within a record vector. Usually
|
||
records are used because they can store several fields of data for each
|
||
observation. Here we have only one, but we use the class anyway to
|
||
inherit its atomicity.</p>
|
||
<div class="sourceCode" id="cb53"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb53-1"><a href="#cb53-1" tabindex="-1"></a>poly <span class="ot"><-</span> <span class="cf">function</span>(...) {</span>
|
||
<span id="cb53-2"><a href="#cb53-2" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast_common</span>(..., <span class="at">.to =</span> <span class="fu">integer</span>())</span>
|
||
<span id="cb53-3"><a href="#cb53-3" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">new_poly</span>(x)</span>
|
||
<span id="cb53-4"><a href="#cb53-4" tabindex="-1"></a> <span class="fu">new_rcrd</span>(<span class="fu">list</span>(<span class="at">data =</span> x), <span class="at">class =</span> <span class="st">"vctrs_poly"</span>)</span>
|
||
<span id="cb53-5"><a href="#cb53-5" tabindex="-1"></a>}</span>
|
||
<span id="cb53-6"><a href="#cb53-6" tabindex="-1"></a>format.vctrs_poly <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb53-7"><a href="#cb53-7" tabindex="-1"></a> <span class="fu">format</span>(<span class="fu">field</span>(x, <span class="st">"data"</span>))</span>
|
||
<span id="cb53-8"><a href="#cb53-8" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>The new <code>format()</code> method delegates to the one we wrote
|
||
for the internal list. The vector looks just like before:</p>
|
||
<div class="sourceCode" id="cb54"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb54-1"><a href="#cb54-1" tabindex="-1"></a>p <span class="ot"><-</span> <span class="fu">poly</span>(<span class="dv">1</span>, <span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">0</span>, <span class="dv">0</span>, <span class="dv">0</span>, <span class="dv">2</span>), <span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">0</span>, <span class="dv">1</span>))</span>
|
||
<span id="cb54-2"><a href="#cb54-2" tabindex="-1"></a>p</span>
|
||
<span id="cb54-3"><a href="#cb54-3" tabindex="-1"></a><span class="co">#> <vctrs_poly[3]></span></span>
|
||
<span id="cb54-4"><a href="#cb54-4" tabindex="-1"></a><span class="co">#> [1] 1 1⋅x^4 + 2 1⋅x^2 + 1</span></span></code></pre></div>
|
||
<p>Making the class atomic means that <code>obj_is_list()</code> now
|
||
returns <code>FALSE</code>. This prevents recursive algorithms that
|
||
traverse lists from reaching too far inside the polynomial
|
||
internals.</p>
|
||
<div class="sourceCode" id="cb55"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb55-1"><a href="#cb55-1" tabindex="-1"></a><span class="fu">obj_is_list</span>(p)</span>
|
||
<span id="cb55-2"><a href="#cb55-2" tabindex="-1"></a><span class="co">#> [1] FALSE</span></span></code></pre></div>
|
||
<p>Most importantly, it prevents users from reaching into the internals
|
||
with <code>[[</code>:</p>
|
||
<div class="sourceCode" id="cb56"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb56-1"><a href="#cb56-1" tabindex="-1"></a>p[[<span class="dv">2</span>]]</span>
|
||
<span id="cb56-2"><a href="#cb56-2" tabindex="-1"></a><span class="co">#> <vctrs_poly[1]></span></span>
|
||
<span id="cb56-3"><a href="#cb56-3" tabindex="-1"></a><span class="co">#> [1] 1⋅x^4 + 2</span></span></code></pre></div>
|
||
</div>
|
||
<div id="implementing-equality-and-comparison" class="section level4">
|
||
<h4>Implementing equality and comparison</h4>
|
||
<p>Equality works out of the box because we can tell if two integer
|
||
vectors are equal:</p>
|
||
<div class="sourceCode" id="cb57"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb57-1"><a href="#cb57-1" tabindex="-1"></a>p <span class="sc">==</span> <span class="fu">poly</span>(<span class="fu">c</span>(<span class="dv">1</span>, <span class="dv">0</span>, <span class="dv">1</span>))</span>
|
||
<span id="cb57-2"><a href="#cb57-2" tabindex="-1"></a><span class="co">#> [1] FALSE FALSE TRUE</span></span></code></pre></div>
|
||
<p>We can’t compare individual elements, because the data is stored in a
|
||
list and by default lists are not comparable:</p>
|
||
<div class="sourceCode" id="cb58"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb58-1"><a href="#cb58-1" tabindex="-1"></a>p <span class="sc"><</span> p[<span class="dv">2</span>]</span>
|
||
<span id="cb58-2"><a href="#cb58-2" tabindex="-1"></a><span class="co">#> Error in `vec_proxy_compare()`:</span></span>
|
||
<span id="cb58-3"><a href="#cb58-3" tabindex="-1"></a><span class="co">#> ! `vec_proxy_compare.vctrs_poly_list()` not supported.</span></span></code></pre></div>
|
||
<p>To enable comparison, we implement a <code>vec_proxy_compare()</code>
|
||
method:</p>
|
||
<div class="sourceCode" id="cb59"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb59-1"><a href="#cb59-1" tabindex="-1"></a>vec_proxy_compare.vctrs_poly <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb59-2"><a href="#cb59-2" tabindex="-1"></a> <span class="co"># Get the list inside the record vector</span></span>
|
||
<span id="cb59-3"><a href="#cb59-3" tabindex="-1"></a> x_raw <span class="ot"><-</span> <span class="fu">vec_data</span>(<span class="fu">field</span>(x, <span class="st">"data"</span>))</span>
|
||
<span id="cb59-4"><a href="#cb59-4" tabindex="-1"></a></span>
|
||
<span id="cb59-5"><a href="#cb59-5" tabindex="-1"></a> <span class="co"># First figure out the maximum length</span></span>
|
||
<span id="cb59-6"><a href="#cb59-6" tabindex="-1"></a> n <span class="ot"><-</span> <span class="fu">max</span>(<span class="fu">vapply</span>(x_raw, length, <span class="fu">integer</span>(<span class="dv">1</span>)))</span>
|
||
<span id="cb59-7"><a href="#cb59-7" tabindex="-1"></a></span>
|
||
<span id="cb59-8"><a href="#cb59-8" tabindex="-1"></a> <span class="co"># Then expand all vectors to this length by filling in with zeros</span></span>
|
||
<span id="cb59-9"><a href="#cb59-9" tabindex="-1"></a> full <span class="ot"><-</span> <span class="fu">lapply</span>(x_raw, <span class="cf">function</span>(x) <span class="fu">c</span>(<span class="fu">rep</span>(<span class="dv">0</span><span class="dt">L</span>, n <span class="sc">-</span> <span class="fu">length</span>(x)), x))</span>
|
||
<span id="cb59-10"><a href="#cb59-10" tabindex="-1"></a></span>
|
||
<span id="cb59-11"><a href="#cb59-11" tabindex="-1"></a> <span class="co"># Then turn into a data frame</span></span>
|
||
<span id="cb59-12"><a href="#cb59-12" tabindex="-1"></a> <span class="fu">as.data.frame</span>(<span class="fu">do.call</span>(rbind, full))</span>
|
||
<span id="cb59-13"><a href="#cb59-13" tabindex="-1"></a>}</span>
|
||
<span id="cb59-14"><a href="#cb59-14" tabindex="-1"></a></span>
|
||
<span id="cb59-15"><a href="#cb59-15" tabindex="-1"></a>p <span class="sc"><</span> p[<span class="dv">2</span>]</span>
|
||
<span id="cb59-16"><a href="#cb59-16" tabindex="-1"></a><span class="co">#> [1] TRUE FALSE TRUE</span></span></code></pre></div>
|
||
<p>Often, this is sufficient to also implement <code>sort()</code>.
|
||
However, for lists, there is already a default
|
||
<code>vec_proxy_order()</code> method that sorts by first
|
||
occurrence:</p>
|
||
<div class="sourceCode" id="cb60"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb60-1"><a href="#cb60-1" tabindex="-1"></a><span class="fu">sort</span>(p)</span>
|
||
<span id="cb60-2"><a href="#cb60-2" tabindex="-1"></a><span class="co">#> <vctrs_poly[3]></span></span>
|
||
<span id="cb60-3"><a href="#cb60-3" tabindex="-1"></a><span class="co">#> [1] 1 1⋅x^2 + 1 1⋅x^4 + 2</span></span>
|
||
<span id="cb60-4"><a href="#cb60-4" tabindex="-1"></a><span class="fu">sort</span>(p[<span class="fu">c</span>(<span class="dv">1</span><span class="sc">:</span><span class="dv">3</span>, <span class="dv">1</span><span class="sc">:</span><span class="dv">2</span>)])</span>
|
||
<span id="cb60-5"><a href="#cb60-5" tabindex="-1"></a><span class="co">#> <vctrs_poly[5]></span></span>
|
||
<span id="cb60-6"><a href="#cb60-6" tabindex="-1"></a><span class="co">#> [1] 1 1 1⋅x^2 + 1 1⋅x^4 + 2 1⋅x^4 + 2</span></span></code></pre></div>
|
||
<p>To ensure consistency between ordering and comparison, we forward
|
||
<code>vec_proxy_order()</code> to <code>vec_proxy_compare()</code>:</p>
|
||
<div class="sourceCode" id="cb61"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb61-1"><a href="#cb61-1" tabindex="-1"></a>vec_proxy_order.vctrs_poly <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb61-2"><a href="#cb61-2" tabindex="-1"></a> <span class="fu">vec_proxy_compare</span>(x, ...)</span>
|
||
<span id="cb61-3"><a href="#cb61-3" tabindex="-1"></a>}</span>
|
||
<span id="cb61-4"><a href="#cb61-4" tabindex="-1"></a></span>
|
||
<span id="cb61-5"><a href="#cb61-5" tabindex="-1"></a><span class="fu">sort</span>(p)</span>
|
||
<span id="cb61-6"><a href="#cb61-6" tabindex="-1"></a><span class="co">#> <vctrs_poly[3]></span></span>
|
||
<span id="cb61-7"><a href="#cb61-7" tabindex="-1"></a><span class="co">#> [1] 1 1⋅x^2 + 1 1⋅x^4 + 2</span></span></code></pre></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="arithmetic" class="section level2">
|
||
<h2>Arithmetic</h2>
|
||
<p>vctrs also provides two mathematical generics that allow you to
|
||
define a broad swath of mathematical behaviour at once:</p>
|
||
<ul>
|
||
<li><p><code>vec_math(fn, x, ...)</code> specifies the behaviour of
|
||
mathematical functions like <code>abs()</code>, <code>sum()</code>, and
|
||
<code>mean()</code>. (Note that <code>var()</code> and <code>sd()</code>
|
||
can’t be overridden, see <code>?vec_math()</code> for the complete list
|
||
supported by <code>vec_math()</code>.)</p></li>
|
||
<li><p><code>vec_arith(op, x, y)</code> specifies the behaviour of the
|
||
arithmetic operations like <code>+</code>, <code>-</code>, and
|
||
<code>%%</code>. (See <code>?vec_arith()</code> for the complete
|
||
list.)</p></li>
|
||
</ul>
|
||
<p>Both generics define the behaviour for multiple functions because
|
||
<code>sum.vctrs_vctr(x)</code> calls
|
||
<code>vec_math.vctrs_vctr("sum", x)</code>, and <code>x + y</code> calls
|
||
<code>vec_math.x_class.y_class("+", x, y)</code>. They’re accompanied by
|
||
<code>vec_math_base()</code> and <code>vec_arith_base()</code> which
|
||
make it easy to call the underlying base R functions.</p>
|
||
<p><code>vec_arith()</code> uses double dispatch and needs the following
|
||
standard boilerplate:</p>
|
||
<div class="sourceCode" id="cb62"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb62-1"><a href="#cb62-1" tabindex="-1"></a>vec_arith.MYCLASS <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb62-2"><a href="#cb62-2" tabindex="-1"></a> <span class="fu">UseMethod</span>(<span class="st">"vec_arith.MYCLASS"</span>, y)</span>
|
||
<span id="cb62-3"><a href="#cb62-3" tabindex="-1"></a>}</span>
|
||
<span id="cb62-4"><a href="#cb62-4" tabindex="-1"></a>vec_arith.MYCLASS.default <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb62-5"><a href="#cb62-5" tabindex="-1"></a> <span class="fu">stop_incompatible_op</span>(op, x, y)</span>
|
||
<span id="cb62-6"><a href="#cb62-6" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Correctly exporting <code>vec_arith()</code> methods from a package
|
||
is currently a little awkward. See the instructions in the Arithmetic
|
||
section of the “Implementing a vctrs S3 class in a package” section
|
||
below.</p>
|
||
<div id="cached-sum-class" class="section level3">
|
||
<h3>Cached sum class</h3>
|
||
<p>I showed an example of <code>vec_math()</code> to define
|
||
<code>sum()</code> and <code>mean()</code> methods for
|
||
<code>cached_sum</code>. Now let’s talk about exactly how it works. Most
|
||
<code>vec_math()</code> functions will have a similar form. You use a
|
||
switch statement to handle the methods that you care about and fall back
|
||
to <code>vec_math_base()</code> for those that you don’t care about.</p>
|
||
<div class="sourceCode" id="cb63"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb63-1"><a href="#cb63-1" tabindex="-1"></a>vec_math.vctrs_cached_sum <span class="ot"><-</span> <span class="cf">function</span>(.fn, .x, ...) {</span>
|
||
<span id="cb63-2"><a href="#cb63-2" tabindex="-1"></a> <span class="cf">switch</span>(.fn,</span>
|
||
<span id="cb63-3"><a href="#cb63-3" tabindex="-1"></a> <span class="at">sum =</span> <span class="fu">attr</span>(.x, <span class="st">"sum"</span>),</span>
|
||
<span id="cb63-4"><a href="#cb63-4" tabindex="-1"></a> <span class="at">mean =</span> <span class="fu">attr</span>(.x, <span class="st">"sum"</span>) <span class="sc">/</span> <span class="fu">length</span>(.x),</span>
|
||
<span id="cb63-5"><a href="#cb63-5" tabindex="-1"></a> <span class="fu">vec_math_base</span>(.fn, .x, ...)</span>
|
||
<span id="cb63-6"><a href="#cb63-6" tabindex="-1"></a> )</span>
|
||
<span id="cb63-7"><a href="#cb63-7" tabindex="-1"></a>}</span></code></pre></div>
|
||
</div>
|
||
<div id="meter-class" class="section level3">
|
||
<h3>Meter class</h3>
|
||
<p>To explore the infix arithmetic operators exposed by
|
||
<code>vec_arith()</code> I’ll create a new class that represents a
|
||
measurement in <code>meter</code>s:</p>
|
||
<div class="sourceCode" id="cb64"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb64-1"><a href="#cb64-1" tabindex="-1"></a>new_meter <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb64-2"><a href="#cb64-2" tabindex="-1"></a> <span class="fu">stopifnot</span>(<span class="fu">is.double</span>(x))</span>
|
||
<span id="cb64-3"><a href="#cb64-3" tabindex="-1"></a> <span class="fu">new_vctr</span>(x, <span class="at">class =</span> <span class="st">"vctrs_meter"</span>)</span>
|
||
<span id="cb64-4"><a href="#cb64-4" tabindex="-1"></a>}</span>
|
||
<span id="cb64-5"><a href="#cb64-5" tabindex="-1"></a></span>
|
||
<span id="cb64-6"><a href="#cb64-6" tabindex="-1"></a>format.vctrs_meter <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb64-7"><a href="#cb64-7" tabindex="-1"></a> <span class="fu">paste0</span>(<span class="fu">format</span>(<span class="fu">vec_data</span>(x)), <span class="st">" m"</span>)</span>
|
||
<span id="cb64-8"><a href="#cb64-8" tabindex="-1"></a>}</span>
|
||
<span id="cb64-9"><a href="#cb64-9" tabindex="-1"></a></span>
|
||
<span id="cb64-10"><a href="#cb64-10" tabindex="-1"></a>meter <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb64-11"><a href="#cb64-11" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast</span>(x, <span class="fu">double</span>())</span>
|
||
<span id="cb64-12"><a href="#cb64-12" tabindex="-1"></a> <span class="fu">new_meter</span>(x)</span>
|
||
<span id="cb64-13"><a href="#cb64-13" tabindex="-1"></a>}</span>
|
||
<span id="cb64-14"><a href="#cb64-14" tabindex="-1"></a></span>
|
||
<span id="cb64-15"><a href="#cb64-15" tabindex="-1"></a>x <span class="ot"><-</span> <span class="fu">meter</span>(<span class="dv">1</span><span class="sc">:</span><span class="dv">10</span>)</span>
|
||
<span id="cb64-16"><a href="#cb64-16" tabindex="-1"></a>x</span>
|
||
<span id="cb64-17"><a href="#cb64-17" tabindex="-1"></a><span class="co">#> <vctrs_meter[10]></span></span>
|
||
<span id="cb64-18"><a href="#cb64-18" tabindex="-1"></a><span class="co">#> [1] 1 m 2 m 3 m 4 m 5 m 6 m 7 m 8 m 9 m 10 m</span></span></code></pre></div>
|
||
<p>Because <code>meter</code> is built on top of a double vector, basic
|
||
mathematic operations work:</p>
|
||
<div class="sourceCode" id="cb65"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb65-1"><a href="#cb65-1" tabindex="-1"></a><span class="fu">sum</span>(x)</span>
|
||
<span id="cb65-2"><a href="#cb65-2" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb65-3"><a href="#cb65-3" tabindex="-1"></a><span class="co">#> [1] 55 m</span></span>
|
||
<span id="cb65-4"><a href="#cb65-4" tabindex="-1"></a><span class="fu">mean</span>(x)</span>
|
||
<span id="cb65-5"><a href="#cb65-5" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb65-6"><a href="#cb65-6" tabindex="-1"></a><span class="co">#> [1] 5.5 m</span></span></code></pre></div>
|
||
<p>But we can’t do arithmetic:</p>
|
||
<div class="sourceCode" id="cb66"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb66-1"><a href="#cb66-1" tabindex="-1"></a>x <span class="sc">+</span> <span class="dv">1</span></span>
|
||
<span id="cb66-2"><a href="#cb66-2" tabindex="-1"></a><span class="co">#> Error in `vec_arith()`:</span></span>
|
||
<span id="cb66-3"><a href="#cb66-3" tabindex="-1"></a><span class="co">#> ! <vctrs_meter> + <double> is not permitted</span></span>
|
||
<span id="cb66-4"><a href="#cb66-4" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">10</span>) <span class="sc">+</span> <span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb66-5"><a href="#cb66-5" tabindex="-1"></a><span class="co">#> Error in `vec_arith()`:</span></span>
|
||
<span id="cb66-6"><a href="#cb66-6" tabindex="-1"></a><span class="co">#> ! <vctrs_meter> + <vctrs_meter> is not permitted</span></span>
|
||
<span id="cb66-7"><a href="#cb66-7" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">10</span>) <span class="sc">*</span> <span class="dv">3</span></span>
|
||
<span id="cb66-8"><a href="#cb66-8" tabindex="-1"></a><span class="co">#> Error in `vec_arith()`:</span></span>
|
||
<span id="cb66-9"><a href="#cb66-9" tabindex="-1"></a><span class="co">#> ! <vctrs_meter> * <double> is not permitted</span></span></code></pre></div>
|
||
<p>To allow these infix functions to work, we’ll need to provide
|
||
<code>vec_arith()</code> generic. But before we do that, let’s think
|
||
about what combinations of inputs we should support:</p>
|
||
<ul>
|
||
<li><p>It makes sense to add and subtract meters: that yields another
|
||
meter. We can divide a meter by another meter (yielding a unitless
|
||
number), but we can’t multiply meters (because that would yield an
|
||
area).</p></li>
|
||
<li><p>For a combination of meter and number multiplication and division
|
||
by a number are acceptable. Addition and subtraction don’t make much
|
||
sense as we, strictly speaking, are dealing with objects of different
|
||
nature.</p></li>
|
||
</ul>
|
||
<p><code>vec_arith()</code> is another function that uses double
|
||
dispatch, so as usual we start with a template.</p>
|
||
<div class="sourceCode" id="cb67"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb67-1"><a href="#cb67-1" tabindex="-1"></a>vec_arith.vctrs_meter <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb67-2"><a href="#cb67-2" tabindex="-1"></a> <span class="fu">UseMethod</span>(<span class="st">"vec_arith.vctrs_meter"</span>, y)</span>
|
||
<span id="cb67-3"><a href="#cb67-3" tabindex="-1"></a>}</span>
|
||
<span id="cb67-4"><a href="#cb67-4" tabindex="-1"></a>vec_arith.vctrs_meter.default <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb67-5"><a href="#cb67-5" tabindex="-1"></a> <span class="fu">stop_incompatible_op</span>(op, x, y)</span>
|
||
<span id="cb67-6"><a href="#cb67-6" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Then write the method for two meter objects. We use a switch
|
||
statement to cover the cases we care about and
|
||
<code>stop_incompatible_op()</code> to throw an informative error
|
||
message for everything else.</p>
|
||
<div class="sourceCode" id="cb68"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb68-1"><a href="#cb68-1" tabindex="-1"></a>vec_arith.vctrs_meter.vctrs_meter <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb68-2"><a href="#cb68-2" tabindex="-1"></a> <span class="cf">switch</span>(</span>
|
||
<span id="cb68-3"><a href="#cb68-3" tabindex="-1"></a> op,</span>
|
||
<span id="cb68-4"><a href="#cb68-4" tabindex="-1"></a> <span class="st">"+"</span> <span class="ot">=</span> ,</span>
|
||
<span id="cb68-5"><a href="#cb68-5" tabindex="-1"></a> <span class="st">"-"</span> <span class="ot">=</span> <span class="fu">new_meter</span>(<span class="fu">vec_arith_base</span>(op, x, y)),</span>
|
||
<span id="cb68-6"><a href="#cb68-6" tabindex="-1"></a> <span class="st">"/"</span> <span class="ot">=</span> <span class="fu">vec_arith_base</span>(op, x, y),</span>
|
||
<span id="cb68-7"><a href="#cb68-7" tabindex="-1"></a> <span class="fu">stop_incompatible_op</span>(op, x, y)</span>
|
||
<span id="cb68-8"><a href="#cb68-8" tabindex="-1"></a> )</span>
|
||
<span id="cb68-9"><a href="#cb68-9" tabindex="-1"></a>}</span>
|
||
<span id="cb68-10"><a href="#cb68-10" tabindex="-1"></a></span>
|
||
<span id="cb68-11"><a href="#cb68-11" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">10</span>) <span class="sc">+</span> <span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb68-12"><a href="#cb68-12" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb68-13"><a href="#cb68-13" tabindex="-1"></a><span class="co">#> [1] 11 m</span></span>
|
||
<span id="cb68-14"><a href="#cb68-14" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">10</span>) <span class="sc">-</span> <span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb68-15"><a href="#cb68-15" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb68-16"><a href="#cb68-16" tabindex="-1"></a><span class="co">#> [1] 9 m</span></span>
|
||
<span id="cb68-17"><a href="#cb68-17" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">10</span>) <span class="sc">/</span> <span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb68-18"><a href="#cb68-18" tabindex="-1"></a><span class="co">#> [1] 10</span></span>
|
||
<span id="cb68-19"><a href="#cb68-19" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">10</span>) <span class="sc">*</span> <span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb68-20"><a href="#cb68-20" tabindex="-1"></a><span class="co">#> Error in `vec_arith()`:</span></span>
|
||
<span id="cb68-21"><a href="#cb68-21" tabindex="-1"></a><span class="co">#> ! <vctrs_meter> * <vctrs_meter> is not permitted</span></span></code></pre></div>
|
||
<p>Next we write the pair of methods for arithmetic with a meter and a
|
||
number. These are almost identical, but while <code>meter(10) / 2</code>
|
||
makes sense, <code>2 / meter(10)</code> does not (and neither do
|
||
addition and subtraction). To support both doubles and integers as
|
||
operands, we dispatch over <code>numeric</code> here instead of
|
||
<code>double</code>.</p>
|
||
<div class="sourceCode" id="cb69"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb69-1"><a href="#cb69-1" tabindex="-1"></a>vec_arith.vctrs_meter.numeric <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb69-2"><a href="#cb69-2" tabindex="-1"></a> <span class="cf">switch</span>(</span>
|
||
<span id="cb69-3"><a href="#cb69-3" tabindex="-1"></a> op,</span>
|
||
<span id="cb69-4"><a href="#cb69-4" tabindex="-1"></a> <span class="st">"/"</span> <span class="ot">=</span> ,</span>
|
||
<span id="cb69-5"><a href="#cb69-5" tabindex="-1"></a> <span class="st">"*"</span> <span class="ot">=</span> <span class="fu">new_meter</span>(<span class="fu">vec_arith_base</span>(op, x, y)),</span>
|
||
<span id="cb69-6"><a href="#cb69-6" tabindex="-1"></a> <span class="fu">stop_incompatible_op</span>(op, x, y)</span>
|
||
<span id="cb69-7"><a href="#cb69-7" tabindex="-1"></a> )</span>
|
||
<span id="cb69-8"><a href="#cb69-8" tabindex="-1"></a>}</span>
|
||
<span id="cb69-9"><a href="#cb69-9" tabindex="-1"></a>vec_arith.numeric.vctrs_meter <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb69-10"><a href="#cb69-10" tabindex="-1"></a> <span class="cf">switch</span>(</span>
|
||
<span id="cb69-11"><a href="#cb69-11" tabindex="-1"></a> op,</span>
|
||
<span id="cb69-12"><a href="#cb69-12" tabindex="-1"></a> <span class="st">"*"</span> <span class="ot">=</span> <span class="fu">new_meter</span>(<span class="fu">vec_arith_base</span>(op, x, y)),</span>
|
||
<span id="cb69-13"><a href="#cb69-13" tabindex="-1"></a> <span class="fu">stop_incompatible_op</span>(op, x, y)</span>
|
||
<span id="cb69-14"><a href="#cb69-14" tabindex="-1"></a> )</span>
|
||
<span id="cb69-15"><a href="#cb69-15" tabindex="-1"></a>}</span>
|
||
<span id="cb69-16"><a href="#cb69-16" tabindex="-1"></a></span>
|
||
<span id="cb69-17"><a href="#cb69-17" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">2</span>) <span class="sc">*</span> <span class="dv">10</span></span>
|
||
<span id="cb69-18"><a href="#cb69-18" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb69-19"><a href="#cb69-19" tabindex="-1"></a><span class="co">#> [1] 20 m</span></span>
|
||
<span id="cb69-20"><a href="#cb69-20" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">2</span>) <span class="sc">*</span> <span class="fu">as.integer</span>(<span class="dv">10</span>)</span>
|
||
<span id="cb69-21"><a href="#cb69-21" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb69-22"><a href="#cb69-22" tabindex="-1"></a><span class="co">#> [1] 20 m</span></span>
|
||
<span id="cb69-23"><a href="#cb69-23" tabindex="-1"></a><span class="dv">10</span> <span class="sc">*</span> <span class="fu">meter</span>(<span class="dv">2</span>)</span>
|
||
<span id="cb69-24"><a href="#cb69-24" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb69-25"><a href="#cb69-25" tabindex="-1"></a><span class="co">#> [1] 20 m</span></span>
|
||
<span id="cb69-26"><a href="#cb69-26" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">20</span>) <span class="sc">/</span> <span class="dv">10</span></span>
|
||
<span id="cb69-27"><a href="#cb69-27" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb69-28"><a href="#cb69-28" tabindex="-1"></a><span class="co">#> [1] 2 m</span></span>
|
||
<span id="cb69-29"><a href="#cb69-29" tabindex="-1"></a><span class="dv">10</span> <span class="sc">/</span> <span class="fu">meter</span>(<span class="dv">20</span>)</span>
|
||
<span id="cb69-30"><a href="#cb69-30" tabindex="-1"></a><span class="co">#> Error in `vec_arith()`:</span></span>
|
||
<span id="cb69-31"><a href="#cb69-31" tabindex="-1"></a><span class="co">#> ! <double> / <vctrs_meter> is not permitted</span></span>
|
||
<span id="cb69-32"><a href="#cb69-32" tabindex="-1"></a><span class="fu">meter</span>(<span class="dv">20</span>) <span class="sc">+</span> <span class="dv">10</span></span>
|
||
<span id="cb69-33"><a href="#cb69-33" tabindex="-1"></a><span class="co">#> Error in `vec_arith()`:</span></span>
|
||
<span id="cb69-34"><a href="#cb69-34" tabindex="-1"></a><span class="co">#> ! <vctrs_meter> + <double> is not permitted</span></span></code></pre></div>
|
||
<p>For completeness, we also need
|
||
<code>vec_arith.vctrs_meter.MISSING</code> for the unary <code>+</code>
|
||
and <code>-</code> operators:</p>
|
||
<div class="sourceCode" id="cb70"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb70-1"><a href="#cb70-1" tabindex="-1"></a>vec_arith.vctrs_meter.MISSING <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb70-2"><a href="#cb70-2" tabindex="-1"></a> <span class="cf">switch</span>(op,</span>
|
||
<span id="cb70-3"><a href="#cb70-3" tabindex="-1"></a> <span class="st">`</span><span class="at">-</span><span class="st">`</span> <span class="ot">=</span> x <span class="sc">*</span> <span class="sc">-</span><span class="dv">1</span>,</span>
|
||
<span id="cb70-4"><a href="#cb70-4" tabindex="-1"></a> <span class="st">`</span><span class="at">+</span><span class="st">`</span> <span class="ot">=</span> x,</span>
|
||
<span id="cb70-5"><a href="#cb70-5" tabindex="-1"></a> <span class="fu">stop_incompatible_op</span>(op, x, y)</span>
|
||
<span id="cb70-6"><a href="#cb70-6" tabindex="-1"></a> )</span>
|
||
<span id="cb70-7"><a href="#cb70-7" tabindex="-1"></a>}</span>
|
||
<span id="cb70-8"><a href="#cb70-8" tabindex="-1"></a><span class="sc">-</span><span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb70-9"><a href="#cb70-9" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb70-10"><a href="#cb70-10" tabindex="-1"></a><span class="co">#> [1] -1 m</span></span>
|
||
<span id="cb70-11"><a href="#cb70-11" tabindex="-1"></a><span class="sc">+</span><span class="fu">meter</span>(<span class="dv">1</span>)</span>
|
||
<span id="cb70-12"><a href="#cb70-12" tabindex="-1"></a><span class="co">#> <vctrs_meter[1]></span></span>
|
||
<span id="cb70-13"><a href="#cb70-13" tabindex="-1"></a><span class="co">#> [1] 1 m</span></span></code></pre></div>
|
||
</div>
|
||
</div>
|
||
<div id="implementing-a-vctrs-s3-class-in-a-package" class="section level2">
|
||
<h2>Implementing a vctrs S3 class in a package</h2>
|
||
<p>Defining S3 methods interactively is fine for iteration and
|
||
exploration, but if your class lives in a package, you need to do a few
|
||
more things:</p>
|
||
<ul>
|
||
<li><p>Register the S3 methods by listing them in the
|
||
<code>NAMESPACE</code> file.</p></li>
|
||
<li><p>Create documentation around your methods, for the sake of your
|
||
user and to satisfy <code>R CMD check</code>.</p></li>
|
||
</ul>
|
||
<p>Let’s assume that the <code>percent</code> class is implemented in
|
||
the pizza package in the file <code>R/percent.R</code>. Here we walk
|
||
through the major sections of this hypothetical file. You’ve seen all of
|
||
this code before, but now it’s augmented by the roxygen2 directives that
|
||
produce the correct <code>NAMESPACE</code> entries and help topics.</p>
|
||
<div id="getting-started" class="section level3">
|
||
<h3>Getting started</h3>
|
||
<p>First, the pizza package needs to include vctrs in the
|
||
<code>Imports</code> section of its <code>DESCRIPTION</code> (perhaps by
|
||
calling <code>usethis::use_package("vctrs")</code>. While vctrs is under
|
||
very active development, it probably makes sense to state a minimum
|
||
version.</p>
|
||
<pre><code>Imports:
|
||
a_package,
|
||
another_package,
|
||
...
|
||
vctrs (>= x.y.z),
|
||
...</code></pre>
|
||
<p>Then we make all vctrs functions available within the pizza package
|
||
by including the directive <code>#' @import vctrs</code> somewhere.
|
||
Usually, it’s not good practice to <code>@import</code> the entire
|
||
namespace of a package, but vctrs is deliberately designed with this use
|
||
case in mind.</p>
|
||
<p>Where should we put <code>#' @import vctrs</code>? There are two
|
||
natural locations:</p>
|
||
<ul>
|
||
<li><p>With package-level docs in <code>R/pizza-doc.R</code>. You can
|
||
use <code>usethis::use_package_doc()</code> to initiate this
|
||
package-level documentation.</p></li>
|
||
<li><p>In <code>R/percent.R</code>. This makes the most sense when the
|
||
vctrs S3 class is a modest and self-contained part of the overall
|
||
package.</p></li>
|
||
</ul>
|
||
<p>We also must use one of these locations to dump some internal
|
||
documentation that’s needed to avoid <code>R CMD check</code>
|
||
complaints. We don’t expect any human to ever read this documentation.
|
||
Here’s how this dummy documentation should look, combined with the
|
||
<code>#' @import vctrs</code> directive described above.</p>
|
||
<div class="sourceCode" id="cb72"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb72-1"><a href="#cb72-1" tabindex="-1"></a><span class="co">#' Internal vctrs methods</span></span>
|
||
<span id="cb72-2"><a href="#cb72-2" tabindex="-1"></a><span class="co">#'</span></span>
|
||
<span id="cb72-3"><a href="#cb72-3" tabindex="-1"></a><span class="co">#' @import vctrs</span></span>
|
||
<span id="cb72-4"><a href="#cb72-4" tabindex="-1"></a><span class="co">#' @keywords internal</span></span>
|
||
<span id="cb72-5"><a href="#cb72-5" tabindex="-1"></a><span class="co">#' @name pizza-vctrs</span></span>
|
||
<span id="cb72-6"><a href="#cb72-6" tabindex="-1"></a><span class="cn">NULL</span></span></code></pre></div>
|
||
<p>This should appear in <code>R/pizza-doc.R</code> (package-level docs)
|
||
or in <code>R/percent.R</code> (class-focused file).</p>
|
||
<p>Remember to call <code>devtools::document()</code> regularly, as you
|
||
develop, to regenerate <code>NAMESPACE</code> and the <code>.Rd</code>
|
||
files.</p>
|
||
<p>From this point on, the code shown is expected to appear in
|
||
<code>R/percent.R</code>.</p>
|
||
</div>
|
||
<div id="low-level-and-user-friendly-constructors" class="section level3">
|
||
<h3>Low-level and user-friendly constructors</h3>
|
||
<p>Next we add our constructor:</p>
|
||
<div class="sourceCode" id="cb73"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb73-1"><a href="#cb73-1" tabindex="-1"></a>new_percent <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>()) {</span>
|
||
<span id="cb73-2"><a href="#cb73-2" tabindex="-1"></a> <span class="cf">if</span> (<span class="sc">!</span><span class="fu">is_double</span>(x)) {</span>
|
||
<span id="cb73-3"><a href="#cb73-3" tabindex="-1"></a> <span class="fu">abort</span>(<span class="st">"`x` must be a double vector."</span>)</span>
|
||
<span id="cb73-4"><a href="#cb73-4" tabindex="-1"></a> }</span>
|
||
<span id="cb73-5"><a href="#cb73-5" tabindex="-1"></a> <span class="fu">new_vctr</span>(x, <span class="at">class =</span> <span class="st">"pizza_percent"</span>)</span>
|
||
<span id="cb73-6"><a href="#cb73-6" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Note that the name of the package must be included in the class name
|
||
(<code>pizza_percent</code>), but it does not need to be included in the
|
||
constructor name. You do not need to export the constructor, unless you
|
||
want people to extend your class.</p>
|
||
<p>We can also add a call to <code>setOldClass()</code> for
|
||
compatibility with S4:</p>
|
||
<div class="sourceCode" id="cb74"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb74-1"><a href="#cb74-1" tabindex="-1"></a><span class="co"># for compatibility with the S4 system</span></span>
|
||
<span id="cb74-2"><a href="#cb74-2" tabindex="-1"></a>methods<span class="sc">::</span><span class="fu">setOldClass</span>(<span class="fu">c</span>(<span class="st">"pizza_percent"</span>, <span class="st">"vctrs_vctr"</span>))</span></code></pre></div>
|
||
<p>Because we’ve used a function from the methods package, you’ll also
|
||
need to add methods to <code>Imports</code>, with (e.g.)
|
||
<code>usethis::use_package("methods")</code>. This is a “free”
|
||
dependency because methods is bundled with every R install.</p>
|
||
<p>Next we implement, export, and document a user-friendly helper:
|
||
<code>percent()</code>.</p>
|
||
<div class="sourceCode" id="cb75"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb75-1"><a href="#cb75-1" tabindex="-1"></a><span class="co">#' `percent` vector</span></span>
|
||
<span id="cb75-2"><a href="#cb75-2" tabindex="-1"></a><span class="co">#'</span></span>
|
||
<span id="cb75-3"><a href="#cb75-3" tabindex="-1"></a><span class="co">#' This creates a double vector that represents percentages so when it is</span></span>
|
||
<span id="cb75-4"><a href="#cb75-4" tabindex="-1"></a><span class="co">#' printed, it is multiplied by 100 and suffixed with `%`.</span></span>
|
||
<span id="cb75-5"><a href="#cb75-5" tabindex="-1"></a><span class="co">#'</span></span>
|
||
<span id="cb75-6"><a href="#cb75-6" tabindex="-1"></a><span class="co">#' @param x A numeric vector</span></span>
|
||
<span id="cb75-7"><a href="#cb75-7" tabindex="-1"></a><span class="co">#' @return An S3 vector of class `pizza_percent`.</span></span>
|
||
<span id="cb75-8"><a href="#cb75-8" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb75-9"><a href="#cb75-9" tabindex="-1"></a><span class="co">#' @examples</span></span>
|
||
<span id="cb75-10"><a href="#cb75-10" tabindex="-1"></a><span class="co">#' percent(c(0.25, 0.5, 0.75))</span></span>
|
||
<span id="cb75-11"><a href="#cb75-11" tabindex="-1"></a>percent <span class="ot"><-</span> <span class="cf">function</span>(<span class="at">x =</span> <span class="fu">double</span>()) {</span>
|
||
<span id="cb75-12"><a href="#cb75-12" tabindex="-1"></a> x <span class="ot"><-</span> <span class="fu">vec_cast</span>(x, <span class="fu">double</span>())</span>
|
||
<span id="cb75-13"><a href="#cb75-13" tabindex="-1"></a> <span class="fu">new_percent</span>(x)</span>
|
||
<span id="cb75-14"><a href="#cb75-14" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>(Again note that the package name will appear in the class, but does
|
||
not need to occur in the function, because we can already do
|
||
<code>pizza::percent()</code>; it would be redundant to have
|
||
<code>pizza::pizza_percent()</code>.)</p>
|
||
</div>
|
||
<div id="other-helpers" class="section level3">
|
||
<h3>Other helpers</h3>
|
||
<p>It’s a good idea to provide a function that tests if an object is of
|
||
this class. If you do so, it makes sense to document it with the
|
||
user-friendly constructor <code>percent()</code>:</p>
|
||
<div class="sourceCode" id="cb76"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb76-1"><a href="#cb76-1" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb76-2"><a href="#cb76-2" tabindex="-1"></a><span class="co">#' @rdname percent</span></span>
|
||
<span id="cb76-3"><a href="#cb76-3" tabindex="-1"></a>is_percent <span class="ot"><-</span> <span class="cf">function</span>(x) {</span>
|
||
<span id="cb76-4"><a href="#cb76-4" tabindex="-1"></a> <span class="fu">inherits</span>(x, <span class="st">"pizza_percent"</span>)</span>
|
||
<span id="cb76-5"><a href="#cb76-5" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>You’ll also need to update the <code>percent()</code> documentation
|
||
to reflect that <code>x</code> now means two different things:</p>
|
||
<div class="sourceCode" id="cb77"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb77-1"><a href="#cb77-1" tabindex="-1"></a><span class="co">#' @param x</span></span>
|
||
<span id="cb77-2"><a href="#cb77-2" tabindex="-1"></a><span class="co">#' * For `percent()`: A numeric vector</span></span>
|
||
<span id="cb77-3"><a href="#cb77-3" tabindex="-1"></a><span class="co">#' * For `is_percent()`: An object to test.</span></span></code></pre></div>
|
||
<p>Next we provide the key methods to make printing work. These are S3
|
||
methods, so they don’t need to be documented, but they do need to be
|
||
exported.</p>
|
||
<div class="sourceCode" id="cb78"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb78-1"><a href="#cb78-1" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb78-2"><a href="#cb78-2" tabindex="-1"></a>format.pizza_percent <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb78-3"><a href="#cb78-3" tabindex="-1"></a> out <span class="ot"><-</span> <span class="fu">formatC</span>(<span class="fu">signif</span>(<span class="fu">vec_data</span>(x) <span class="sc">*</span> <span class="dv">100</span>, <span class="dv">3</span>))</span>
|
||
<span id="cb78-4"><a href="#cb78-4" tabindex="-1"></a> out[<span class="fu">is.na</span>(x)] <span class="ot"><-</span> <span class="cn">NA</span></span>
|
||
<span id="cb78-5"><a href="#cb78-5" tabindex="-1"></a> out[<span class="sc">!</span><span class="fu">is.na</span>(x)] <span class="ot"><-</span> <span class="fu">paste0</span>(out[<span class="sc">!</span><span class="fu">is.na</span>(x)], <span class="st">"%"</span>)</span>
|
||
<span id="cb78-6"><a href="#cb78-6" tabindex="-1"></a> out</span>
|
||
<span id="cb78-7"><a href="#cb78-7" tabindex="-1"></a>}</span>
|
||
<span id="cb78-8"><a href="#cb78-8" tabindex="-1"></a></span>
|
||
<span id="cb78-9"><a href="#cb78-9" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb78-10"><a href="#cb78-10" tabindex="-1"></a>vec_ptype_abbr.pizza_percent <span class="ot"><-</span> <span class="cf">function</span>(x, ...) {</span>
|
||
<span id="cb78-11"><a href="#cb78-11" tabindex="-1"></a> <span class="st">"prcnt"</span></span>
|
||
<span id="cb78-12"><a href="#cb78-12" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Finally, we implement methods for <code>vec_ptype2()</code> and
|
||
<code>vec_cast()</code>.</p>
|
||
<div class="sourceCode" id="cb79"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb79-1"><a href="#cb79-1" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb79-2"><a href="#cb79-2" tabindex="-1"></a>vec_ptype2.vctrs_percent.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">new_percent</span>()</span>
|
||
<span id="cb79-3"><a href="#cb79-3" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb79-4"><a href="#cb79-4" tabindex="-1"></a>vec_ptype2.double.vctrs_percent <span class="ot"><-</span> <span class="cf">function</span>(x, y, ...) <span class="fu">double</span>()</span>
|
||
<span id="cb79-5"><a href="#cb79-5" tabindex="-1"></a></span>
|
||
<span id="cb79-6"><a href="#cb79-6" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb79-7"><a href="#cb79-7" tabindex="-1"></a>vec_cast.pizza_percent.pizza_percent <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) x</span>
|
||
<span id="cb79-8"><a href="#cb79-8" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb79-9"><a href="#cb79-9" tabindex="-1"></a>vec_cast.pizza_percent.double <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">percent</span>(x)</span>
|
||
<span id="cb79-10"><a href="#cb79-10" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb79-11"><a href="#cb79-11" tabindex="-1"></a>vec_cast.double.pizza_percent <span class="ot"><-</span> <span class="cf">function</span>(x, to, ...) <span class="fu">vec_data</span>(x)</span></code></pre></div>
|
||
</div>
|
||
<div id="arithmetic-1" class="section level3">
|
||
<h3>Arithmetic</h3>
|
||
<p>Writing double dispatch methods for <code>vec_arith()</code> is
|
||
currently more awkward than writing them for <code>vec_ptype2()</code>
|
||
or <code>vec_cast()</code>. We plan to improve this in the future. For
|
||
now, you can use the following instructions.</p>
|
||
<p>If you define a new type and want to write <code>vec_arith()</code>
|
||
methods for it, you’ll need to provide a new single dispatch S3 generic
|
||
for it of the following form:</p>
|
||
<div class="sourceCode" id="cb80"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb80-1"><a href="#cb80-1" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb80-2"><a href="#cb80-2" tabindex="-1"></a><span class="co">#' @method vec_arith my_type</span></span>
|
||
<span id="cb80-3"><a href="#cb80-3" tabindex="-1"></a>vec_arith.my_type <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb80-4"><a href="#cb80-4" tabindex="-1"></a> <span class="fu">UseMethod</span>(<span class="st">"vec_arith.my_type"</span>, y)</span>
|
||
<span id="cb80-5"><a href="#cb80-5" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>Note that this actually functions as both an S3 method for
|
||
<code>vec_arith()</code> and an S3 generic called
|
||
<code>vec_arith.my_type()</code> that dispatches off <code>y</code>.
|
||
roxygen2 only recognizes it as an S3 generic, so you have to register
|
||
the S3 method part of this with an explicit <code>@method</code>
|
||
call.</p>
|
||
<p>After that, you can define double dispatch methods, but you still
|
||
need an explicit <code>@method</code> tag to ensure it is registered
|
||
with the correct generic:</p>
|
||
<div class="sourceCode" id="cb81"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb81-1"><a href="#cb81-1" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb81-2"><a href="#cb81-2" tabindex="-1"></a><span class="co">#' @method vec_arith.my_type my_type</span></span>
|
||
<span id="cb81-3"><a href="#cb81-3" tabindex="-1"></a>vec_arith.my_type.my_type <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb81-4"><a href="#cb81-4" tabindex="-1"></a> <span class="co"># implementation here</span></span>
|
||
<span id="cb81-5"><a href="#cb81-5" tabindex="-1"></a>}</span>
|
||
<span id="cb81-6"><a href="#cb81-6" tabindex="-1"></a></span>
|
||
<span id="cb81-7"><a href="#cb81-7" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb81-8"><a href="#cb81-8" tabindex="-1"></a><span class="co">#' @method vec_arith.my_type integer</span></span>
|
||
<span id="cb81-9"><a href="#cb81-9" tabindex="-1"></a>vec_arith.my_type.integer <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb81-10"><a href="#cb81-10" tabindex="-1"></a> <span class="co"># implementation here</span></span>
|
||
<span id="cb81-11"><a href="#cb81-11" tabindex="-1"></a>}</span>
|
||
<span id="cb81-12"><a href="#cb81-12" tabindex="-1"></a></span>
|
||
<span id="cb81-13"><a href="#cb81-13" tabindex="-1"></a><span class="co">#' @export</span></span>
|
||
<span id="cb81-14"><a href="#cb81-14" tabindex="-1"></a><span class="co">#' @method vec_arith.integer my_type</span></span>
|
||
<span id="cb81-15"><a href="#cb81-15" tabindex="-1"></a>vec_arith.integer.my_type <span class="ot"><-</span> <span class="cf">function</span>(op, x, y, ...) {</span>
|
||
<span id="cb81-16"><a href="#cb81-16" tabindex="-1"></a> <span class="co"># implementation here</span></span>
|
||
<span id="cb81-17"><a href="#cb81-17" tabindex="-1"></a>}</span></code></pre></div>
|
||
<p>vctrs provides the hybrid S3 generics/methods for most of the base R
|
||
types, like <code>vec_arith.integer()</code>. If you don’t fully import
|
||
vctrs with <code>@import vctrs</code>, then you will need to explicitly
|
||
import the generic you are registering double dispatch methods for with
|
||
<code>@importFrom vctrs vec_arith.integer</code>.</p>
|
||
</div>
|
||
<div id="testing" class="section level3">
|
||
<h3>Testing</h3>
|
||
<p>It’s good practice to test your new class. Specific
|
||
recommendations:</p>
|
||
<ul>
|
||
<li><p><code>R/percent.R</code> is the type of file where you really do
|
||
want 100% test coverage. You can use
|
||
<code>devtools::test_coverage_file()</code> to check this.</p></li>
|
||
<li><p>Make sure to test behaviour with zero-length inputs and missing
|
||
values.</p></li>
|
||
<li><p>Use <code>testthat::verify_output()</code> to test your format
|
||
method. Customised printing is often a primary motivation for creating
|
||
your own S3 class in the first place, so this will alert you to
|
||
unexpected changes in your printed output. Read more about
|
||
<code>verify_output()</code> in the <a href="https://www.tidyverse.org/blog/2019/11/testthat-2-3-0/">testthat
|
||
v2.3.0 blog post</a>; it’s an example of a so-called <a href="https://ro-che.info/articles/2017-12-04-golden-tests">golden
|
||
test</a>.</p></li>
|
||
<li><p>Check for method symmetry; use <code>expect_s3_class()</code>,
|
||
probably with <code>exact = TRUE</code>, to ensure that
|
||
<code>vec_c(x, y)</code> and <code>vec_c(y, x)</code> return the same
|
||
type of output for the important <code>x</code>s and <code>y</code>s in
|
||
your domain.</p></li>
|
||
<li><p>Use <code>testthat::expect_error()</code> to check that inputs
|
||
that can’t be combined fail with an error. Here, you should be generally
|
||
checking the class of the error, not its message. Relevant classes
|
||
include <code>vctrs_error_assert_ptype</code>,
|
||
<code>vctrs_error_assert_size</code>, and
|
||
<code>vctrs_error_incompatible_type</code>.</p>
|
||
<div class="sourceCode" id="cb82"><pre class="sourceCode r"><code class="sourceCode r"><span id="cb82-1"><a href="#cb82-1" tabindex="-1"></a><span class="fu">expect_error</span>(<span class="fu">vec_c</span>(<span class="dv">1</span>, <span class="st">"a"</span>), <span class="at">class =</span> <span class="st">"vctrs_error_incompatible_type"</span>)</span></code></pre></div></li>
|
||
</ul>
|
||
<p>If your tests pass when run by <code>devtools::test()</code>, but
|
||
fail when run in <code>R CMD check</code>, it is very likely to reflect
|
||
a problem with S3 method registration. Carefully check your roxygen2
|
||
comments and the generated <code>NAMESPACE</code>.</p>
|
||
</div>
|
||
<div id="existing-classes" class="section level3">
|
||
<h3>Existing classes</h3>
|
||
<p>Before you build your own class, you might want to consider using, or
|
||
subclassing existing classes. You can check <a href="https://github.com/krlmlr/awesome-vctrs">awesome-vctrs</a> for a
|
||
curated list of R vector classes, some of which are built with
|
||
vctrs.</p>
|
||
<p>If you’ve built or extended a class, consider adding it to that list
|
||
so other people can use it.</p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<!-- code folding -->
|
||
|
||
|
||
<!-- dynamically load mathjax for compatibility with self-contained -->
|
||
<script>
|
||
(function () {
|
||
var script = document.createElement("script");
|
||
script.type = "text/javascript";
|
||
script.src = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
|
||
document.getElementsByTagName("head")[0].appendChild(script);
|
||
})();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|