Найти тему
Vim

VimScript 40 [ Функциональное программирование ]

Оглавление

| ч39. переключатель | Содержание | ч41 пути {path} |

Давайте сделаем небольшое отступление и поговорим о стиле программирования, о котором вы навярняка уже слышали: функциональное программирование.

Если вы программировали на таких языках, как Python, Ruby или Javascript, или Lisp, Scheme, Clojure или Haskell, вы, вероятно, знакомы с идеей использования функций в качестве переменных и использования структур данных с неизменяемым состоянием. Если вы никогда раньше этим не занимались, можете смело пропустить эту главу, но я бы посоветовал вам все равно попробовать и расширить свой кругозор!

В Vimscript есть все элементы, необходимые для программирования в функциональном стиле, но он немного хромой. Мы можем создать несколько костылей, которые уменьшат боль.

Создайте где-нибудь файл с именем functional.vim, чтобы не пришлось набирать один и тот же текст снова и снова. Этот файл будет нашим блокнотом для этой главы.

Неизменяемые структуры данных

К сожалению Vim не имеет встроенных vector или map, но мы можем попробовать реализовать это с помощью функций.

Добавьте в свой файл следующую функцию:

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

Сохраните и запустите исходный файл (:w | source %), а затем введите :echo Sorted([3,2,4,1]), что бы посмотреть что делает наша функция. На выходе получим: [1, 2, 3, 4].

Чем же это отличается от простого вызова встроенной функции sort()? Суть в первой строчке: let new_list = deepcopy(a:l). Vim сортирует список внутри функции не меняя переданный ей список. То есть мы вначале создали копию списка [ let new_list = deepcopy(a:l) ] затем отсортировали копию списка [ call sort(new_list) ] и в конце вернули отсортированный список с помощью [ return new_list ]. Пока должно быть все просто.

Это предотвращает побочные эффекты и помогает нам писать код, который легче анализировать и тестировать. Давайте добавим еще несколько вспомогательных функций в том же стиле:

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

Каждая из этих функций точно такая же как наша первая функция Sorted () за исключением средней строки и аргументов, которые они принимают. Сохраните наш файл и запустите его, и попробуйте функции в действии с несколькими списками.

Reversed( ) принимает список и возвращает новый список с элементами наоборот.

Append( ) возвращает новый список с добавленным заданного значения в конце старого.

Assoc( ) возвращает новый список, в котором элемент с заданным индексом заменяется новым значением.

Pop( ) возвращает новый список с удаленным элементом из заданного индекса

Функции как переменные

Vimscript поддерживает использование переменных для хранения функций, но синтаксис немного туповат. Выполните следующие команды:

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

На выходе получаем: [1, 2, 3]

Получаем на выходе [1, 2, 3]. Та как мы вызвали функцию Append а она добавляет новый элемент в конец списка. Обратите внимание, что переменная, которую мы использовали , начиналась с заглавной буквы. Если переменная Vimscript ссылается на функцию, она должна начинаться с заглавной буквы.

Функции так же могут храниться в списках, как и любые другие переменные. Выполните следующие команды:

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

На выходе получаем: ['a', 'c']

Так как мы вызвали первую функцию Pop а он в свою очередь удалил первый элемент. Думаю не надо говорить что отсчет идет с нуля.

Переменная может начинаться со строчной буквы а не с заглавной так как она хранит список, а не функцию. А содержимое списка не имеет никакого значения.

Функции более высокого порядка

Давайте создадим несколько наиболее часто используемых функций более высокого порядка. Если вы не знакомы с этим термином, функции более высокого порядка - это функции, которые берут другие функции и что-то с ними делают.

Мы начнем с функции map. Добавьте это в свой файл:

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

Сохраните и запустите исходный код. Испытайте его, выполнив следующие команды:

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

На выходе получаем: [[2, 1], [4, 3]]

Итак как же Mapped() работает? Мы снова создали новый список с помощью deepcopy(), что-то делаем с ним и возвращаем измененную копию - ничего нового. Сложная часть - середина.

Mapped() принимает два аргумента: funcref (обозначает "переменную, содержащую функцию") и список. Мы используем встроенную функцию map() для выполнения фактической работы. Прочитайте :help map() сейчас, чтобы увидеть, как это работает.

В двух словах о map (выражение 1, выражение 2). Выражение 1 - должно быть списком, строкой, словарем. Если выражение 1 является списком или словарем, то происходит замена каждого элемента из выражения 1, если выражение 1 строка то происходит замена каждого символа, как будет происходить замена зависит от выражения 2. Выражение 2 может быть либо строкой либо Funcref. Если выражение 2 это строка то v:val это значение текущего элемента. Для словаря, списка, строки - v:key.

Лучшая иллюстрация работы map, выполните этот код пару тройку раз:

:echo map (mylist, '">" . v:val . "<"')

Теперь мы создадим несколько других распространенных функций более высокого порядка. Добавьте в свой файл следующее:

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

Сохраните и запустите исходный код. Испытайте его, выполнив следующие команды:

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

На выходе получаем: [[1, 2], ['foo']]

Filtered() принимает предикат функции и список. Он возвращает копию списка, который содержит только элементы оригинала, где результат вызова функции для него является "истинным". Для этого мы используем встроенную функцию len(), которая может подсчитать количество элементов в списке, она отфильтровала все элементы, длина которых равна нулю.

Наконец, мы создадим аналог Filtered() но наоборот:

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

Сохраните и запустите исходный код. Испытайте его, выполнив следующие команды:

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

На выходе получаем: [[ ], [ ]]

похоже, Filtered() возвращает результат того что ложно. Связанно это с восклицательным знаком который мы добавили в результат вызова. Что бы инвертировать результат его работы.

Производительность

Вы можете подумать, что постоянное копирование списков расточительно, поскольку Vim приходится постоянно создавать новые копии и собирать мусор за старыми.

Да вы правы, эти операции могут быть дорогостоящими. И это будет иметь значение если вы работаете с огромными списками, то да работа может замедлиться. Однако в реальной жизни вы можете быть удивлены тому, какую маленькую разницу вы увидите на самом деле.

Упражнения

Прочти :help sort().

Прочти :help reverse().

Прочти :help copy().

Прочти :help deepcopy().

Прочти :help map(), если вы еще этого не сделали.

Прочти :help function().

Модифицируйте Assoc(), Pop(), Mapped(), Filtered()и Removed()для поддержки словарей. Вам возможно понадобится :help type() .

Реализуйте Reduced().

| ч39. переключатель | Содержание | ч41 пути {path} |