Vim script has a few very basic functional programming facilities.
It has map()
and filter()
, but as far as I know it lacks a reduce()
function. "Reduce" reduces a collection of values to a single value.
Is there a way to create reduce()
or emulate it somehow in Vim script? Is it possible to reduce a list of values, without writing an explicit loop, in a Vim script expression? As an example, is there a way to reduce the first five positive integers over the addition operation, as is par for the course in functional languages?
In JavaScript:
[1, 2, 3, 4, 5].reduce(function(x, y) { return x + y; });
15
In Clojure:
(reduce + (range 1 (inc 5)))
15
In Haskell:
foldl (+) 0 [1..5]
15
In J:
+/>:i.5
15
In Vim script: ...?
For future reference, here are my variations on the theme, inspired by the answers that @MatthewStrawbridge linked.
The expression for the original example problem:
eval(join(range(1, 5), '+'))
A more general solution in the same vein, using Add()
, where a
is range(1, 5)
:
eval(repeat('Add(',len(a)-1).a[0].','.join(a[1:],'),').')')
This constructs the string "Add(Add(Add(Add(1,2),3),4),5)"
, and then eval
s it. Fun!
Finally, Reduce()
, which takes a Funcref and a list, and then reduces it in a loop using Vim's list "destructuring" syntax [x, y; z]
. See :h :let-unpack
.
function! Reduce(f, list)
let [acc; tail] = a:list
while !empty(tail)
let [head; tail] = tail
let acc = a:f(acc, head)
endwhile
return acc
endfunction
And this is how it's used:
:echo Reduce(function('Add'), range(1, 5))
15
I think you're expected to construct a string and then execute it (which I admit feels a bit clunky). The help (:h E714
) gives this example:
:exe 'let sum = ' . join(nrlist, '+')
So in your case, where nrlist
is [1, 2, 3, 4, 5]
, it would construct the string let sum = 1+2+3+4+5
and then execute it.
Alternatively, you can probably code up your own reduce function as there isn't one built in.
Edit:
I found a discussion on the vim_use Google Group (How powerful is language build in vim compare with the language build in emacs?, 25th Jan 2010) about functional programming in Vim, which included a couple of implementations of just such a reduce function.
The first, by Tom Link, is as follows:
function! Reduce(ffn, list) "{{{3
if empty(a:list)
return ''
else
let list = copy(a:list)
let s:acc = remove(list, 0)
let ffn = substitute(a:ffn, '\<v:acc\>', "s:acc", 'g')
for val in list
let s:acc = eval(substitute(ffn, '\<v:val\>', val, 'g'))
endfor
return s:acc
endif
endf
echom Reduce("v:val + v:acc", [1, 2, 3, 4])
echom Reduce("v:val > v:acc ? v:val : v:acc", [1, 2, 3, 4])
echom Reduce("'v:val' < v:acc ? 'v:val' : v:acc", split("characters",
'\zs'))
The second, by Antony Scriven, is as follows:
fun Reduce(funcname, list)
let F = function(a:funcname)
let acc = a:list[0]
for value in a:list[1:]
let acc = F(acc, value)
endfor
return acc
endfun
fun Add(a,b)
return a:a + a:b
endfun
fun Max(a,b)
return a:a > a:b ? a:a : a:b
endfun
fun Min(a,b)
return a:a < a:b ? a:a : a:b
endfun
let list = [1,2,3,4,5]
echo Reduce('Add', list)
echo Reduce('Max', list)
echo Reduce('Min', list)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With