doubt overrides the operator ?
to provide:
The standard usages ?topic
and type?topic
as documented in help("Question")
still work.
We refer to all of those as “dubious”, both as a reference to the package name and to emphasize the fact that they’re not parsed as proper operators.
The syntax that doubts uses is quirky and will be very surprising to many, and possibly blasphemous to some. I don’t expect to see many people sharing code that would use doubt, but it can be convenient for interactive use, personal projects or specific uses that justify designing a DSL.
It also lives as a style exercise of metaprogramming pushed to the extreme.
Install with :
::install_github("moodymudskipper/doubt") remotes
R provides some %foo%
infix operators such as
%in%
, or %/%
, their precedence is between
^
and *
.
Once the package doubt is attached, any accessible function can be called as such operators :
library(doubt, warn.conflicts = FALSE)
1:4 %%head? 2 + 1 # same precedence as standard infix operators
#> [1] 2 3
I we needed an infix operator with a different precedence we could
also use ^head?
, -head?
etc, and the
precedence would be given by the first symbol of the operator.
In practice the syntax %%fun?
will generally be more
useful, but sometimes ~fun?
can be helpful due to its low
precedence, for instance x + y ~saveRDS? file
will save
x + y
where x + y %%saveRDS? file
would have
saved y
.
Unary operators can be used too, which is nice for interactive use as
we spare some parentheses. Unary operators are +
,
-
, !
or ~
. Generally
~
will be more adaped due to its low precedence but
+
is less visually confusing as unary +
is
rarely used in practice.
~head? 1:10
#> [1] 1 2 3 4 5 6
It’s possible to pass more arguments by using {}
on the
rhs, this works with both unary and binary forms and is interesting in
that arguments are separated by new lines (or ;
), and not
by commas, which can be handy in situations where commas can add
confusions, such as benchmarks, R6 definitions, or shiny UI
functions.
"a" +paste? "b"
#> [1] "a b"
+paste?{"a"; "b"}
#> [1] "a b"
"a" +paste?{"b"; "c"}
#> [1] "a b c"
library(microbenchmark)
+microbenchmark?{
= sapply(iris, length)
a= lengths(iris)
b
}
library(shiny)
cat(as.character(
+fluidPage?{
= "Hello Shiny!"
title +fluidRow?{
+column?{
= 4
width "4"
}+column?{
= 3
width = 2
offset "3 offset 2"
}
}
}
))
library(R6)
# example borrowed from https://adv-r.hadley.nz/r6.html and modified
R6Class("Person", +list?{
= NULL
name = NA
age = function(name, age = NA) {
initialize $name <- name
self$age <- age
self
} })
Dubious pipes are similar to magrittr’s pipes, except we can choose their precedence.
Piping with another precedence is useful when working interactively
to avoid editing brackets in many places. A common use case is to avoid
brackets when calling plotly::ggplotly()
on a
ggplot
object. The calls below are indeed equivalent :
library(ggplot2)
library(plotly)
library(magrittr)
# standard use
ggplotly(ggplot(cars, aes(speed, dist)) + geom_point())
ggplot(cars, aes(speed, dist)) + geom_point()) %>% ggplotly()
(# using doubt
ggplot(cars, aes(speed, dist)) + geom_point() ~.? ggplotly(.)
doubt supports the definition of complex syntaxes with very low effort. This is better understood by an example, let’s build a silly function that adds 2 numbers.
"?add: ({x})({y})" <- "{x} + {y}"
: (2)(3)
?add#> [1] 5
We see that the name of the created object contains a glue-like pattern (see package glue from Jim Hester), then value of the object is another glue like string which describes the actual code to execute.
More practical now, say I want to easily View()
the
output of an call, but can’t be bother to wrap it in
View()
, I just want to type “?v” at the end of the
call:
"{expr}?v" <- "View({expr})"
head(cars) ?v # same as View(head(cars))
For it to work the ?
symbol should either be used in its
unary form at the start of the expression as in the former case, or in
its binary form anywhere else outside of ()
,
{}
and control flows. And of course, the expression should
be syntactic.
Other examples, let’s design an alternative for
loop
:
"?FOR? {x} = {from} :step: {by} :to: {to} :do: {expr}" <-
"for({x} in seq({from}, {to}, {by})) {expr}"
= 3 :step: 2 :to: 10 :do: print(z*10)
?FOR? z #> [1] 30
#> [1] 50
#> [1] 70
#> [1] 90
Or a python style function definition :
"def?{nm}({args}):{expr}" <- "{nm} <- function({args}) {expr}"
# test it to define a simple function
area(length=10, width=10):{length * width}
def? area(2)
#> [1] 20
area(3,4)
#> [1] 12
To be recognized by doubt a dubious syntax must either :
To define dubious syntaxes in your package should import
doubt, which you can do by running
devtools::use_package("doubt")
, then I suggest you store
them in a script following this template (if you use
roxygen2):
#' Modified question mark operator
#'
#' Reexported from package *doubt*
#' @inheritParams doubt::`?`
#' @export
`?` <- doubt::`?`
<- function(libname, pkgname) {
.onAttach ::register_dubious_syntaxes(c("?add: ({x})({y})", "{expr}?v"))
doubtinvisible()
}
#' dubious syntaxes
#'
#' @name dubious
NULL
#' @export
#' @rdname dubious
#' @usage add: (x)(y)
#' @examples
#' ?add: (1)(2)
"?add: ({x})({y})" <- "{x} + {y}"
#' @export
#' @rdname dubious
#' @usage expr?v
"{expr}?v" <- "View({expr})"
In this example, we document on the same page both syntaxes. Note
that they must be registered in the definition of
.onAttach()
.