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 :
remotes::install_github("moodymudskipper/doubt")
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?{
a= sapply(iris, length)
b= lengths(iris)
}
library(shiny)
cat(as.character(
+fluidPage?{
title = "Hello Shiny!"
+fluidRow?{
+column?{
width = 4
"4"
}
+column?{
width = 3
offset = 2
"3 offset 2"
}
}
}
))
library(R6)
# example borrowed from https://adv-r.hadley.nz/r6.html and modified
R6Class("Person", +list?{
name = NULL
age = NA
initialize = function(name, age = NA) {
self$name <- name
self$age <- age
}
})
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}"
?add: (2)(3)
#> [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}"
?FOR? z = 3 :step: 2 :to: 10 :do: print(z*10)
#> [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
def? area(length=10, width=10):{length * width}
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::`?`
.onAttach <- function(libname, pkgname) {
doubt::register_dubious_syntaxes(c("?add: ({x})({y})", "{expr}?v"))
invisible()
}
#' 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()
.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.