README.md

Some Clojure-inspired functions (macros) in R

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):

New functions (macros) may be added gradually in the future.

Installation

devtools::install_github('alekrutkowski/clojR')

Demo

library(clojR)

cond and related

# 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"))

case and related

# 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

Threading (the same role as the pipe operators)

# 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

Conditional threading

# 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.


alekrutkowski/clojR documentation built on May 11, 2019, 11:24 p.m.