Skip to content

Instantly share code, notes, and snippets.

@houshuang
Last active August 29, 2015 14:02
Show Gist options
  • Save houshuang/6ff2cb250a91d94ae70f to your computer and use it in GitHub Desktop.
Save houshuang/6ff2cb250a91d94ae70f to your computer and use it in GitHub Desktop.
Making lambdas much more dense through some ugly string manipulation + eval
fn <- function(x) {
if(class(x) == "function") return(x) else {
print(class(x))
# escape percentage marks within strings
x = gsub("'(.+)?%([1-9]?)(.+)?'","'\\1^^^\\2\\3'", x)
# if a single word, call one argument, otherwise analyze arguments
if(grepl("^[a-zA-Z.]*$", x)) { x = paste0(x,"(x1)")} else {
x = gsub("%([^1-9])", "%1\\1", x)
x = gsub("'", "\"", x)
if(!grepl("%1", x)) { x = paste0("%1", x)}
vars = str_extract_all(string=x, pattern="%[1-9]")
arity = max(
unlist(lapply(vars, function(x) {
as.numeric(str_extract(string=x, pattern="\\d"))
})))
args = Reduce(init=c(), x=(1:arity), f=function(y,x) {c(y, paste0("x", x))})
for(e in 0:arity) {
x = gsub(paste0("%", as.character(e)), paste0("x", as.character(e)), x)
}
}
x = gsub("\\^\\^\\^([1-9]?)", "%\\1", x)
fn = paste0("function(", toString(args), ") {", x , "}")
eval(parse(text=fn))
}}
map = fn("unlist(Map(fn(%1), %2))")
reduce = function(f, acc=c(), x,...) Reduce(fn(f), init=acc, x=x)
filter = function(f, x) unlist(Filter(fn(f),x))
P = fn("as.character")
p0 = paste0
@houshuang
Copy link
Author

Why? Simply because I wanted something shorter :) I know it's silly, but I am so used to Haskell, where you would do

folder + 0 [1 2 3] #-> 6

or Clojure where you can do

(map #(% * 2) [1 2 3]) #-> [2 4 6]

or Apple's new Swift where you can do

[1,2,3,4].map {$0 * 2}

After that, lapply(c(1,2,3), function(x) x * 2) seems too verbose :)

@houshuang
Copy link
Author

Examples

> reduce("c(%, paste0('x', %2))", c(), 1:9)
[1] "x1" "x2" "x3" "x4" "x5" "x6" "x7" "x8" "x9"

> map("paste0('x', %)", 1:9)
[1] "x1" "x2" "x3" "x4" "x5" "x6" "x7" "x8" "x9"

> filter(">2", c(1,2,3,4))
[1] 3 4

> reduce("%+%2", 0, c(1,2,3))
[1] 6

@houshuang
Copy link
Author

I added a special case if the function definition is a single word only consisting of lower-case or upper-case letters. This allows me to do map("class", dataframe), which is turned into function(x) class(x). I can still do map("*2", c(1,2,3)), which turns into function(x) x*2. If there's any ambiguity, you can always specify the % arguments, which will override this test.

I also check if % is within a string, and ignore it if it is. And I added some abbreviation for common functions I use. This makes me able to quickly generate a list of percentages like below.

> map("p0('%', %)", seq(0,100,25))
[1] "%0"   "%25"  "%50"  "%75"  "%100"

@houshuang
Copy link
Author

OK, of course you can just pass a function to map directly, without wrapping it. I now check in fn if it has been passed a function, and if that is the case, it's returned directly. Thus I can call map(class, df) and map("class", df), and they both work. At least this project is helping me learn more about the built-in functionality in R as well :)

@houshuang
Copy link
Author

One interesting weakness is that the eval happens in the context of the fn function, not in the original context, so for example this

get_filenames(p) %as% map("p0(p, '/', %)", 
    filter("grepl('^[^~]', %)", list.files(path=p)))

does not work properly, because p in the map function is not defined when the built-up string gets evaluated by fn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment