Introduction#🠑
Have you ever been frustrated at R, maybe after reading too many rude StackOverflow or R-help threads? (me too) Well, what if we took R and instead of helping making it accessible and fun, made all of those esoteric, argon-y, incredibly annoying, and unreadable parts much worse?
Enter reso
, a library and R-based programming style. Only
the following symbols from R are allowed:
(), [, ], !, 0, +, and “,” (the comma).
Along with four functions to make reso
“go.” They are
really just simplified and narrower versions of existing R
utilities:
s(): An alias for
substr(string, i)
. Used to get a letter from a stringp(): An alias for
paste(..., collapse = "")
, to concatenate stringsx(): Interpret and execute a string as an R expression
d(): an alias for deparse()
And that’s it! Not even spaces, line breaks, or tabs are allowed! Happy programming :)
Setup and installation#🠑
For now, you can install the package from the R console with
devtools::install_github("18kimn/reso")
. I’ve submitted
the package to CRAN, so hopefully this can soon be updated!
You can view the package implementation details at github.com/18kimn/reso.
How it works#🠑
There’s a few more things that should be said.
This style is able to work because R is able to interpret strings as expressions, and through hacky (some might say c l e v e r) tricks you can grab those strings from native R objects that are available to you.
The general pattern using this is as follows:
- Find a hacky way to coerce values to built-in objects,
like
FALSE
orlogical(0)
- Interpret that value as a string with
d()
, so thatFALSE
becomes"FALSE"
and so on. - Pull a letter of that string out with
s()
- Repeat and combine with
p()
until we can make a function name - Evaluate the string as an R expression with
x()
. - If that worked, you have a new function available to you! Repeat ad infinitum until your code is completely nonsensical.
Caveats#🠑
There are some things that I’m not sure if the style can do
as of now. Probably the biggest issue is that a programmer
can’t save variables to the namespace. The reso_validate()
function is super hacky, so much so that it just marks as
valid those strings or file expressions which have only
reso
characters. This means that assignment to new
characters is not allowed. It’d also break because the d()
function operates in the parent frame instead of in the
global environment with no escape hatch. The parent frame
would most often be the context inside of x()
and would be
“forgotten” after execution, meaning that variables can’t
really be saved.
A demonstration#🠑
Goal: Generate the output [1] 0 1
In other words, create the output we would receive if we
were typing c(0, 1)
in vanilla R.
Let’s start by getting just the letter “c” as a string, which we can do by:
Produce
character(0)
by generating an empty string with R’spaste
or reso’sp
, then get the 0th element of that result.Convert the value
character(0)
into character, e.g."character(0)"
. This can be done with R’sdeparse
or reso’sd
.Get the first letter of “character(0)”, via R’s
substr
or reso’ss
.
Those steps as code:
p()[0]
#> character(0)
d(0[0])
#> [1] "numeric(0)"
s(d(p()[0]))
#> [1] "c"
Next, we’d want to turn this string into the R expression
c(0, 1)
. To do this, we can:
- Force R to interpret “c” as the native function
c
, by usingparse
and theneval
in R, or using thex
function1 in reso. - Generate 1 by coercing 0 to
TRUE
with!0
, and combiningTRUE
withnumeric(0)
to forceTRUE
to be numeric (or 1). This requires our new version ofc()
. - Combine all of our above steps with a set of parens and a 0!
x(s(d(p()[0])))
#> function (...) .Primitive("c")
x(s(d(p()[0])))(!0,0[0])
#> [1] 1
x(s(d(p()[0])))(0,x(s(d(p()[0])))(!0,0[0]))
#> [1] 0 1
Yay!
Expanding to almost the entire R universe#🠑
Part 1: The alphabet#🠑
This could have been a bit of a puzzle to figure out, and potentially impossible. R has a fair number of native values, but many letters are just hard to figure out or produce via type coercion.
Luckily, R provides an object that makes this almost
trivial: the letters
array!
To get it, we need:
- “l”, from
logical(0)
- Two “e”s, from
integer(0)
- Two “t”s, from
integer(0)
- One “r”, from
integer(0)
- One “s”, from the eleventh letter of the deparse
function expression for
s()
.2
The code representation of this is below. c()
and numbers
are used instead of their reso
equivalents for
readability.3
s(d(!0[0])) # the first letter of "logical(0)", or "l"
#> [1] "l"
c() + !0 # makes integer(0) by adding TRUE and NULL
#> integer(0)
s(d(c() + !0), 4) # turns integer(0) -> "integer(0)" -> "e"
#> [1] "e"
s(d(c() + !0), 3) #"t"
#> [1] "t"
s(d(c() + !0), 7) #"r"
#> [1] "r"
s(d(s)[1], 11) #"s"
#> [1] "s"
#combining
x(p(
s(d(!0[0])),
s(d(c() + !0), 4),
s(d(c() + !0), 3),
s(d(c() + !0), 3),
s(d(c() + !0), 4),
s(d(c() + !0), 7),
s(d(s)[1], 11)
))
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
We can subset letters from here in order to form arbitrary words, and thus nearly all R commands.
Part 2: Non-alphabetical characters#🠑
The last trick in getting this to be an almost-all-of-R
style is in using the builtins
vector. builtins
contains
the names of all built-in functions in R, many of which
include non-alphabetical operators like +, -, (, :, and so
on. You could subset the builtins array for these, which
would then unlock the door to almost all of R.
Some notes about motivation and learning#🠑
This is not meant to be a useful or practical programming tool. It is meant to show the opposite, that being esoteric is useless and using simpler, abstracted logic can be way more helpful.4
Compared to languages like JavaScript, R stands out in being more accessible and friendly. Sometimes programmers attempt to be as concise as possible, preferring syntax like arrow functions to jam as much code as possible into a single line. Logical operators like ‘&&’ and ‘||’ are used via a side effect as if statements themselves instead of as just logical operators.
In contrast, the R community tends to view being verbose as a feature. The base R libraries export thousands of functions to handle many general cases and provide a higher level of abstraction. Pipes in R are encouraged in order to demystify code and reduce mistakes, and tools like the R linter even encourage placing every statement after a pipe on a new line to encourage even more readability.
Of course, R still has lots of room to improve in becoming accessible. Let’s help each other get there by making the R community welcoming and fun!