Aleksander Rutkowski 2016-08-09
Clojure is a modern Lisp dialect. Due to its Lisp/Scheme roots, R is somewhat similar (functional programming with immutable data; macros a.k.a. non-standard evaluation a.k.a. code as data a.k.a. homoiconicity), but lacks some useful and terse language constructs. This is an attempt to implement them in R.
The implemented functions (macros):
cond
and related -- see https://clojuredocs.org/clojure.core/condcase
and related -- see https://clojuredocs.org/clojure.core/caseas->
-- see https://clojuredocs.org/clojure.core/as-%3E->
-- see https://clojuredocs.org/clojure.core/-%3E->>
-- see https://clojuredocs.org/clojure.core/-%3E%3Econd->
and related -- generalised version inspired by https://clojuredocs.org/clojure.core/cond-%3Edoc
-- see https://clojuredocs.org/clojure.repl/doc
New functions (macros) may be added gradually in the future.
devtools::install_github('alekrutkowski/clojR')
library(clojR)
# Also vectorised `condv` is available (based on base::ifelse).
# Example based on the first example from
# https://clojuredocs.org/clojure.core/cond
# Transformed to:
# if (n < 0) "negative" else if (n > 0) "positive" else "zero"
`pos-neg-or-zero` <- function(n)
cond(n < 0, 'negative',
n > 0, 'positive',
'zero')
`pos-neg-or-zero`(5)
## [1] "positive"
`pos-neg-or-zero`(-1)
## [1] "negative"
`pos-neg-or-zero`(0)
## [1] "zero"
# condv(n < 0, 'negative',
# n > 0, 'positive',
# 'zero')
# would transform to:
# ifelse(n < 0, "negative", ifelse(n > 0, "positive", "zero"))
# More powerful than base::switch
# not only letters or integers can be used as keys:
f <- base::mean
case(f,
stats::median, 1,
base::mean, 2,
3)
## [1] 2
# Different vectorised variants of case
# (using `ifelse` instead of `if` under the hood):
x <- c(1/0, as.numeric(NA), NaN, 0)
# `casevid`
# based on vectorised base::identical
casevid(x,
Inf, "infinity",
as.numeric(NA), "not available",
"other")
## [1] "infinity" "not available" "other" "other"
# `caseveq`
# based on `==` (equality)
# less general than `casevid` but likely faster
caseveq(x,
Inf, "infinity",
as.numeric(NA), "not available",
"other")
## [1] "infinity" NA NA NA
# Possible alternatives to pipe operators
# like %>% in package `magrittr`
# `Thread-first` -- transformed to:
# sum(3 - 400, 7, 8)
`->`(3,
`-`(400),
sum(7,8))
## [1] -382
# `Thread-last` -- transformed to:
# sum(7, 8, 400 - 3)
`->>`(3,
`-`(400),
sum(7,8))
## [1] 412
# `Thread-as` -- transformed to:
# sum(c(mean(c(1, 1000)), 5)) - 13
`as->`(1000, nn,
mean(c(1,nn)),
sum(c(nn,5)),
`-`(nn,13))
## [1] 492.5
# Slightly modified (generalised) compared to the Clojure version as it is threading the
# a value not only through the right-side expresions but also through
# the conditions/tests (while in Clojure these are static conditions):
`cond->`(1, # we start with 1
`==`(1), `+`(1), # 1==1 is true so 1+1 is evaluated and yields 2 which is threded further
`<`(0), `*`(42), # 2<0 is false so the operation is skipped
`==`(2), `*`(3)) # 2==2 is true so 2*3 is evaluated and it finally yields 6
## [1] 6
# A version closer in spirit to the Clojure example -- the constants need to be wrapped
# in anonymous functions or in a function which ignores its first argument
# (see `constant` below):
`cond->`(1, # we start with 1
function(x) TRUE, `+`(1), # the condition is true so 1+1 yields 2
function(x) FALSE, `*`(42), # the condition is false so the operation is skipped
function(x) 2==2, `*`(3)) # 2==2 so it yields 6
## [1] 6
constant <- function(ignore_me, v) v
`cond->`(1, # we start with 1
constant(TRUE), `+`(1), # the condition is true so 1+1 yields 2
constant(FALSE), `*`(42), # the condition is false so the operation is skipped
constant(2==2), `*`(3)) # 2==2 so it yields 6
## [1] 6
# Use inside a function:
zz <- function(v,n)
`cond->`(v,
constant(n>1), `+`(1),
constant(n>2), `+`(2),
constant(n>3), `+`(3))
zz(10, 2.5)
## [1] 13
# Also a vectorised version `condv->` using internally `ifelse` rather than `if`
# is available.
REPL-related
# Extraction of inline "docstrings":
# an informal documentation, e.g. for
# functions not in a package.
# Use the double hash character (##)
# for the comments to be detected by `doc`.
fff <- function(x = 1) {
## This is my function:
## argument x, default 1.
## returns x + 10.
x + 10
}
doc(fff)
## This is my function:
## argument x, default 1.
## returns x + 10.