-
-
Save houshuang/6ff2cb250a91d94ae70f to your computer and use it in GitHub Desktop.
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 |
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
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"
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 :)
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
.
Why? Simply because I wanted something shorter :) I know it's silly, but I am so used to Haskell, where you would do
or Clojure where you can do
or Apple's new Swift where you can do
After that,
lapply(c(1,2,3), function(x) x * 2)
seems too verbose :)