R Syntax Extensions

  • Most proposals have working implementations linked
  • Examples from other languages
  • Proposed R Solution

Raw strings

Example - Python

print(r"a / raw string with \ some 'quotations'")
print(r'Python has four forms to handle "different" quotes')
print(r"""Including the 'triple' "quoted" form""")
## a / raw string with \ some 'quotations'
## Python has four forms to handle "different" quotes
## Including the 'triple' "quoted" form

Example - Perl

print q{Perl \ lets you delimit 'with' "paired" delimiters };
print q#and alternatively any 7-bit \Ascii character#
## Perl \ lets you delimit 'with' "paired" delimiters and alternatively any 7-bit \Ascii character

Proposed Syntax

# Raw strings are very useful in regular expressions
gsub(r"\\", "/", r"\a\new\string")
## [1] "/a/new/string"
# Or windows file paths
r'C:\foo\bar'
## [1] "C:\\foo\\bar"

String interpolation

Example - Bash

value="foo"
echo "value = $value"
## value = foo
foo <- 1
bar <- 2
paste0("foo = '", foo, "'
bar = '", bar, "'")
## [1] "foo = '1'\nbar = '2'"
sprintf("foo = '%s'\nbar = '%s'", foo, bar)
## [1] "foo = '1'\nbar = '2'"

Proposed Syntax

#install.package("glue")
g <- glue::glue

g("foo = '{foo}'
   bar = '{bar}'")
## foo = '1'
## bar = '2'
g("1 + 1 = {a <- 1; a + 1}")
## 1 + 1 = 2
g("foo = '{a}'
   bar = '{b}'",
  a = format(foo, nsmall = 2),
  b = bar + 2)
## foo = '1.00'
## bar = '4'
gd <- glue::glue_data
mtcars$name <- rownames(mtcars)
gd(head(mtcars), "The {name} has {mpg} mpg and {hp} hp.")
## The Mazda RX4 has 21 mpg and 110 hp.
## The Mazda RX4 Wag has 21 mpg and 110 hp.
## The Datsun 710 has 22.8 mpg and 93 hp.
## The Hornet 4 Drive has 21.4 mpg and 110 hp.
## The Hornet Sportabout has 18.7 mpg and 175 hp.
## The Valiant has 18.1 mpg and 105 hp.

Vs sprintf()

  • No explicit typing
  • Implicitly documented
  • Maintain variable locality

Unary Specials

Proposed Syntax

`%chr%` <- as.character

%chr% 1
## [1] "1"

Advantages

  • Allows user defined binary %foo% in unary contexts.
  • Most useful for operator that make sense in binary and unary contexts.
`%g%` <- function(x, y) {
  env <- parent.frame()
  if (missing(y)) {
    eval(bquote(glue::glue(.(x), .envir = env)))
  } else {
    eval(bquote(glue::glue_data(x, .(y), .envir = env)))
  }
}
x <- 10
%g% "x = {x}"
## x = 10
head(mtcars) %g% "The {name} has {mpg} and {hp} hp."
## The Mazda RX4 has 21 and 110 hp.
## The Mazda RX4 Wag has 21 and 110 hp.
## The Datsun 710 has 22.8 and 93 hp.
## The Hornet 4 Drive has 21.4 and 110 hp.
## The Hornet Sportabout has 18.7 and 175 hp.
## The Valiant has 18.1 and 105 hp.
library("magrittr")
mtcars %>% head %>% summary
##       mpg             cyl         disp             hp       
##  Min.   :18.10   Min.   :4   Min.   :108.0   Min.   : 93.0  
##  1st Qu.:19.27   1st Qu.:6   1st Qu.:160.0   1st Qu.:106.2  
##  Median :21.00   Median :6   Median :192.5   Median :110.0  
##  Mean   :20.50   Mean   :6   Mean   :211.8   Mean   :117.2  
##  3rd Qu.:21.30   3rd Qu.:6   3rd Qu.:249.8   3rd Qu.:110.0  
##  Max.   :22.80   Max.   :8   Max.   :360.0   Max.   :175.0  
##       drat             wt             qsec             vs     
##  Min.   :2.760   Min.   :2.320   Min.   :16.46   Min.   :0.0  
##  1st Qu.:3.098   1st Qu.:2.684   1st Qu.:17.02   1st Qu.:0.0  
##  Median :3.500   Median :3.045   Median :17.82   Median :0.5  
##  Mean   :3.440   Mean   :2.988   Mean   :18.13   Mean   :0.5  
##  3rd Qu.:3.888   3rd Qu.:3.384   3rd Qu.:19.23   3rd Qu.:1.0  
##  Max.   :3.900   Max.   :3.460   Max.   :20.22   Max.   :1.0  
##        am           gear          carb           name          
##  Min.   :0.0   Min.   :3.0   Min.   :1.000   Length:6          
##  1st Qu.:0.0   1st Qu.:3.0   1st Qu.:1.000   Class :character  
##  Median :0.5   Median :3.5   Median :1.500   Mode  :character  
##  Mean   :0.5   Mean   :3.5   Mean   :2.167                     
##  3rd Qu.:1.0   3rd Qu.:4.0   3rd Qu.:3.500                     
##  Max.   :1.0   Max.   :4.0   Max.   :4.000
sum <- . %>% head %>% summary
sum
## Functional sequence with the following components:
## 
##  1. head(.)
##  2. summary(.)
## 
## Use 'functions' to extract the individual functions.
# devtools::install_github("jimhester/magrittr@unary")
sum <- %>% head %>% summary
sum
## Functional sequence with the following components:
## 
##  1. head(.)
##  2. summary(.)
## 
## Use 'functions' to extract the individual functions.

Brackets for List Instantiation

Example - Ruby

days = [
  "Monday",
  "Tuesday",
  "Wednesday",
  [1, 2, 3],
]
print(days)
## ["Monday", "Tuesday", "Wednesday", [1, 2, 3]]

Proposed Syntax

days <- [
  "Monday",
  "Tuesday",
  "Wednesday",
  [1, 2, 3]
]
days
## [[1]]
## [1] "Monday"
## 
## [[2]]
## [1] "Tuesday"
## 
## [[3]]
## [1] "Wednesday"
## 
## [[4]]
## [[4]][[1]]
## [1] 1
## 
## [[4]][[2]]
## [1] 2
## 
## [[4]][[3]]
## [1] 3

foreach and Generator functions

Example - Python

import warnings
def yrange(n):
    i = 0
    while i < n:
        yield i
        i += 1

y = yrange(3)
print(y)
print(y.next())
print(y.next())
print(y.next())
try:
    print(y.next())
except StopIteration as e:
    print("Iterator done")

# Within a loop
for i in yrange(3):
    print(i)
## <generator object yrange at 0x1100a2aa0>
## 0
## 1
## 2
## Iterator done
## 0
## 1
## 2
yrange <- function(n) {
  i <- 1
  function() {
    if (i <= n) {
      i <<- i + 1
      return(i - 1)
    }
    return(FALSE)
  }
}
y = yrange(3)
y()
## [1] 1
y()
## [1] 2
y()
## [1] 3
y()
## [1] FALSE
y <- yrange(3)
while (i <- y()) {
  print(i)
}
## [1] 1
## [1] 2
## [1] 3

Proposed Syntax

foreach (i in yrange(3)) {
  print(i)
}

#> [1] 1
#> [1] 2
#> [1] 3
  • Implement with Condition objects
  • Symmetry with current for syntax.

Type Annotations

Example - Python 3

def greeting(name: str) -> str:
    hi: str = 'Hello '
    return hi + name

print(greeting("Jim"))

Proposed Syntax

# install.packages("types")
suppressPackageStartupMessages(library(types))
greeting <- function(name = ? str) {
  hi <- "Hello " ? str
  paste0(hi, name)
} ? str
greeting()
## Error in paste0(hi, name): argument is missing, with no default
greeting("Jim")
## [1] "Hello Jim"
  • mypy Static type checker for Python.
  • Backwards compatible - no parser modifications
  • typeCheck - Add runtime checks based on type annotations.
  • Future
  • Auto generate documentation
  • Static type checker
  • Compiler optimizations

Native pipe operator

Example - sh

ps aux | grep -w R | awk '{print $2}' | head
## 97417
## 86924
## 80481
## 73483
## 69774
## 69537
## 73808
## 58578
## 97450
## 97449

Long traceback's with magrittr implementation hard to understand.

library(magrittr)
fail <- function(...) stop("fail")
mtcars %>% lapply(fail) %>% unlist()
traceback()
#> 14: stop("fail") at #1
#> 13: FUN(X[[i]], ...)
#> 12: lapply(., fail)
#> 11: function_list[[1L]](value)
#> 10: unlist(.)
#> 9: function_list[[1L]](value)
#> 8: freduce(value, `_function_list`)
#> 7: Recall(function_list[[1L]](value), function_list[-1L])
#> 6: freduce(value, `_function_list`)
#> 5: `_fseq`(`_lhs`)
#> 4: eval(quote(`_fseq`(`_lhs`)), env, env)
#> 3: eval(quote(`_fseq`(`_lhs`)), env, env)
#> 2: withVisible(eval(quote(`_fseq`(`_lhs`)), env, env))
#> 1: mtcars %>% lapply(fail) %>% unlist()

Proposed Syntax

mtcars >> head()
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
##                                name
## Mazda RX4                 Mazda RX4
## Mazda RX4 Wag         Mazda RX4 Wag
## Datsun 710               Datsun 710
## Hornet 4 Drive       Hornet 4 Drive
## Hornet Sportabout Hornet Sportabout
## Valiant                     Valiant
mtcars >> 
  subset(cyl == 4) >>
  lm(mpg ~ disp, data = _)
## 
## Call:
## lm(formula = mpg ~ disp, data = subset(mtcars, cyl == 4))
## 
## Coefficients:
## (Intercept)         disp  
##     40.8720      -0.1351
# Generated AST contains only nested calls
quote(mtcars >> 
  subset(cyl == 4) >>
  lm(mpg ~ disp, data = _))
## lm(mpg ~ disp, data = subset(mtcars, cyl == 4))
mtcars >> lapply(fail) >> unlist()
traceback()
#> 4: stop("fail") at #1
#> 3: FUN(X[[i]], ...)
#> 2: lapply(mtcars, fail)
#> 1: unlist(lapply(mtcars, fail))
  • Better error messages.
  • Much better tracebacks on failure.
  • Consistent use of _ as the pipe placeholder instead of ..

Trailing Commas

Example - C99

  • C (99),C++, perl, python, ruby, julia, ...

    enum {
      foo,
      bar,
      baz,
    } e;
c(
  1,
  3,
  2,
)
## [1] 1 3 2
#> Error in c(1, 2, 3, ) : argument 4 is empty
c(1,
  2,
  3,
  NULL)
## [1] 1 2 3
list(1,
     2,
     3,
     NULL)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 3
## 
## [[4]]
## NULL
c(1,
  2,
  3,
  list())
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
## 
## [[3]]
## [1] 3

Proposed Syntax

c(
  1,
  2,
  3,
)
## [1] 1 2 3

Compact anonymous functions

Example - C++11

/* engine.opts = "-x c++ -std=c++11 -o lambda" */
#include <iostream>
#include <functional>

int main()
{
  auto f = [](int i) { return i + 4; };
  int num;
  std::cin >> num;
  std::cout << f(num) << '\n';
}
echo 0 | ./lambda
echo 10 | ./lambda
## 4
## 14

Proposed Syntax

# devtools::install_github("jimhester/lambda")
library("lambda")
fun <- f({.(x) + .(y = 1)})
fun(1)
## [1] 2
fun(1, 2)
## [1] 3
lapply(1:3, f({.(x) + .(y)}), y = 3)
## [[1]]
## [1] 4
## 
## [[2]]
## [1] 5
## 
## [[3]]
## [1] 6
x <- list(1, NULL, 2)
compact <- f(Filter(Negate(is.null), .(x)))
base::print.function(compact)
## function (x) 
## Filter(Negate(is.null), x)
compact(x)
## [[1]]
## [1] 1
## 
## [[2]]
## [1] 2
`%f%` <- lambda::f
%f% { .(x) + .(y = 2) }
## function (x, y = 2) 
## {
##     x + y
## }

Triple-Quoted String Literals

# install.packages("glue")
t <- glue:::trim

t("hello")
## [1] "hello"
t("
hello")
## [1] "hello"
t("

hello")
## [1] "\nhello"
t("
  Hello,
    World.
  ")
## [1] "Hello,\n  World."

Proposed syntax

f <- function() {
  """
  Hello,
    World.
  """
}

Variable unquoting / tidy evaluation

Example - SQL

library("DBI")
con <- DBI::dbConnect(odbc::odbc(), "MySQL")
mtcars$name <- row.names(mtcars)
dbWriteTable(con, "mtcars", mtcars, overwrite = TRUE)
dbExecute(con, "SET @cyl = 8, @hp = 225")
## [1] 0
dbGetQuery(con, "SELECT name FROM mtcars WHERE cyl = @cyl AND hp > @hp")
##                name
## 1        Duster 360
## 2 Chrysler Imperial
## 3        Camaro Z28
## 4    Ford Pantera L
## 5     Maserati Bora
suppressPackageStartupMessages(library(rlang))
var <- "x"
value = "y"
data.frame(exprs(!!var := !!value))
##   x
## 1 y

Proposed Syntax

var <- "x"
value = "y"
data.frame(@var = @value)

Variable unpacking

Example - Ruby

data = ['a', 'b', 'c']
foo, bar, baz = data
puts(foo, bar, baz)
## a
## b
## c
`%<-%` <- function(lhs, rhs) {
  lhs <- substitute(lhs)[-1L]
  for (i in seq_along(lhs)) {
    assign(as.character(lhs[[i]]), rhs[[i]], envir = parent.frame())
  }
}
data <- c("a", "b", "c")
c(foo, bar, baz) %<-% data
foo; bar; baz
## [1] "a"
## [1] "b"
## [1] "c"

Proposed syntax

foo, bar, baz <- data