Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Vimscript functional programming


May 24, 2021 Vim


Table of contents


Now let's take a break and talk about a programming style you might have heard: functional programming.

If you've ever used Python, Ruby, or Javascript, or even _Lisp, Scheme, Clojure, or Haskell, you should think it's normal to use functions as variable types and immvable states as data structures. If you haven't used it, you can safely skip this chapter, but I encourage you to take the opportunity to try it and broaden your horizons.

Vimscript has the potential to program in a functional style, but it can be a bit of a labor force. We can create some auxiliary functions to make the process less painful.

Go ahead and create functional.vim file so you don't have to hit every line of code again and again. This document will be a draft of this chapter.

Immedible data structure

Unfortunately, Vim doesn't have an immvable set like Clojure's built-in vector and map, but with some auxiliary functions, we can simulate it to some extent.

Add the following function to your file:

function! Sorted(l)
    let new_list = deepcopy(a:l)
    call sort(new_list)
    return new_list
endfunction

Save and source the file, :echo Sorted([3,2,4,1]) try it out. Vim [1,2,3,4]

What's the difference between sort() function? T he key is the first let new_list = deepcopy(a:l) Vim's sort() re-lists, so we first create a copy of the list and sort the copies so that the original list is not changed.

This avoids side effects and helps us write code that is easier to infer and test. Let's add more auxiliary functions of the same style:

function! Reversed(l)
    let new_list = deepcopy(a:l)
    call reverse(new_list)
    return new_list
endfunction

function! Append(l, val)
    let new_list = deepcopy(a:l)
    call add(new_list, a:val)
    return new_list
endfunction

function! Assoc(l, i, val)
    let new_list = deepcopy(a:l)
    let new_list[a:i] = a:val
    return new_list
endfunction

function! Pop(l, i)
    let new_list = deepcopy(a:l)
    call remove(new_list, a:i)
    return new_list
endfunction

Each function is the same except for the middle line and the arguments they accept. Save and source files and try them on some lists.

Reversed() accepts a list and returns a new list of inverted elements.

Append() a new list that adds a given value to the original list.

Assoc() abbreviation for "associate") returns a new list of elements on a given index that have been replaced with new values.

Pop() a new list of elements removed from a given index.

As a function of a variable

Vimscript supports the use of variable storage functions, but the relevant syntax is a bit dull. Execute the following command:

:let Myfunc = function("Append")
:echo Myfunc([1, 2], 3)

Vim is expected to [1, 2, 3] . N ote that the variables we use begin with capital letters. If a Vimscript variable is to refer to a function, it begins with a capital letter.

Like other kinds of variables, functions can also be stored in a list. Follow these commands:

:let funcs = [function("Append"), function("Pop")]
:echo funcs[1](['a', 'b', 'c'], 1)

Vim shows ['a', 'c'] funcs variable, No, needs to start with a capital letter because it stores a list, not a function. The contents of the list have no effect.

Higher-order functions

Let's create some versatile high-order functions. If you need to explain, higher-order functions are functions that accept and use other functions.

We'll map the map function. Add this to your file:

function! Mapped(fn, l)
    let new_list = deepcopy(a:l)
    call map(new_list, string(a:fn) . '(v:val)')
    return new_list
endfunction

Save and source the file and try the following command:

:let mylist = [[1, 2], [3, 4]]
:echo Mapped(function("Reversed"), mylist)

Vim [[2, 1], [4, 3]] of Reversed() to each element in the list.

Mapped() work? O nce again, we use deepcopy() a new list, modify it, and return a modified copy -- nothing new. There is a doorway is the middle part.

Mapped() accepts two parameters: a funcref (the term "store variables for a function" in Vim) and a list. W e use the built-in map() function to do real work. Read :help map() see how it works.

Now we'll create some generic high-order functions. Add the following code to your file:

function! Filtered(fn, l)
    let new_list = deepcopy(a:l)
    call filter(new_list, string(a:fn) . '(v:val)')
    return new_list
endfunction

Try Filtered()

:let mylist = [[1, 2], [], ['foo'], []]
:echo Filtered(function('len'), mylist)

Vim [[1, 2], ['foo']]

Filtered() accepts a predicate function and a list. I t returns a copy of a list that includes only elements that use themselves as input parameters for predicate functions and return true values. Here we use the len() to filter out all elements with a length of 0.

Finally, we Filtered() a good friend (counterpart):

function! Removed(fn, l)
    let new_list = deepcopy(a:l)
    call filter(new_list, '!' . string(a:fn) . '(v:val)')
    return new_list
endfunction

Try it like you did with Filtered()

:let mylist = [[1, 2], [], ['foo'], []]
:echo Removed(function('len'), mylist)

Vim [[], []] Removed() Filtered() it only retains elements that the predicate function returns as non-true values.

The only difference in the code is the call to the '!' . which reverses the result of the predicate function.

Efficiency

Given that Vim has to keep creating new copies and garbage collecting old objects, you might think it's a waste to keep making copies.

Yes, you're right! Vim's list doesn't support structural sharing like Clojure's vector, so all the replication here is expensive.

Sometimes it's a real problem. I f you need to use a large list, the program will slow down as a result. In the real world, you may be surprised to find that you hardly notice the difference.

Think about it: When I was writing this chapter, Vim was using 80M of memory (and I had a bunch of plug-ins installed). M y notebook has a total _8G_ memory. A re some copies of the list created, which can make a perceptible difference? Of course it depends on the size of the list, but in most cases the answer will be No.

For comparison, my Firefox opened five tabs and is now _1.22G_ memory.

You will need to decide for yourself when this programming style will lead to unacceptable inefficiencies.

Practice

Read :help sort()

Read :help reverse()

Read :help copy()

Read :help deepcopy()

Read :help map() you haven't read it.

Read :help function()

Modify Assoc() Pop() Mapped() Filtered() Removed() to support dictionary types. You may need to :help type() help yourself.

Implement Reduced()

Pour yourself a favorite drink. This chapter is really intense( intense)!