Vimscript supports map and filter—higher-order functions (aka functions that operate on functions). Both of these functions take either a list or a dictionary as a first argument, and a function as a second.
For instance, if we wanted to filter out every animal name that is not proper, we would write a filter function:
function IsProperName(name)
if a:name =~? '(Mr|Miss) .+'
return 1
endif
return 0
endfunction
IsProperName will return 1 (true) if the name starts with Mr or Miss (which is, as we know, a proper form to address an animal), and 0 (false) otherwise.
Now, given the following dictionary:
let animal_names = {
'cat': 'Miss Cattington',
'dog': 'Mr Dogson',
'parrot': 'Polly'
}
We will write a filter function that will only leave key or value pairs with proper names:
call filter(animal_names, 'IsProperName(v:val)')
And we'll do this to see that it worked:
:echo animal_names
{'cat': 'Miss Cattington', 'dog': 'Mr Dogson'}
If you're coming from other programming languages, this syntax probably feels somewhat awkward. The second argument to the filter function is a string, which gets evaluated for every key value pair of the dictionary. Here, v:val will get expanded to the dictionary value (while v:key could be used to access the key).
The second argument to filter can also be a function reference. Vim lets you reference a function like this:
let IsProperName2 = function('IsProperName')
Now, you can call IsProperName2 just like you would IsProperName:
:echo IsProperName2('Mr Dogson')
1
This can be used to pass functions around as arguments to any function:
function FunctionCaller(func, arg)
return a:func(a:arg)
endfunction
Try running it:
:echo FunctionCaller(IsProperName2, 'Miss Catington')
1
Armed with this knowledge, we can also pass a function reference as a second argument to the filter function. However, if we decide to pass a function reference, we have to change our original function to take two arguments: the key and the value for the dictionary (in that order):
function IsProperNameKeyValue(key, value)
if a:value =~? '(Mr|Miss) .+'
return 1
endif
return 0
endfunction
Now, we can execute the filter function as follows:
call filter(animal_names, function('IsProperNameKeyValue'))
And to validate that it works, let's echo the animal_names dictionary:
:echo animal_names
{'cat': 'Miss Cattington', 'dog': 'Mr Dogson'}
When operating on lists, v:key refers to a item index, and v:val refers to the item value.
The map function behaves in a similar manner. It lets you modify each list item or dictionary value.
For example, let's make every name proper in the following list:
let animal_names = ['Miss Cattington', 'Mr Dogson', 'Polly', 'Meowtington']
In this exercise, we'll reuse the IsProperName function from the earlier example.
Lambdas come in especially useful with this type of function. Here's how we prefix 'Miss ' to a name if it's not proper:
call map(animal_names,
{key, val -> IsProperName(val) ? val : 'Miss ' . val})
Verify the results are as expected:
:echo animal_names
['Miss Cattington', 'Mr Dogson', 'Miss Polly, 'Miss Meowtington']
This Lambda is the equivalent of the following:
function MakeProperName(name)
if IsProperName(a:name)
return a:name
endif
return 'Miss ' . a:name
endfunction
call map(animal_names, 'MakeProperName(v:val)')
The map function can be called with a function reference the same way a filter is. The mapping function will still take two values (key and value for dictionaries, and index and value for lists).