A & ldquo; Reduce & rdquo; Function in the Vim script

advertisements

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: ...?


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)