Хабрахабр

Функциональные интерфейсы… в VBA

"…те, кто не прочь поглазеть на любителя прилюдно свалять дурака, пусть понаблюдают, как я доказываю, что Java и Visual Basic – близнецы, разлученные при рождении, а С++ им даже не дальний родственник."

Брюс Мак-Кинни “Крепкий орешек Visual Basic”

Введение

Постоянный интерес к подходам функционального программирования в настоящее время приводит к тому, что традиционные языки программирования активно обзаводятся функциональными средствами. И, хотя чистые функциональные языки остаются пока не слишком популярными, функциональные возможности прочно обосновались в таких языках, как С++, Java, JavaScript, Python и др. Язык VBA уже многие годы пользуется заслуженной популярностью у довольно многочисленной аудитории пользователей Microsoft Office, однако этот язык практически не содержит функциональных средств.
Давайте попытаемся заполнить этого пробел – предлагаю законченную (хотя, возможно, и не безупречную) реализацию функциональных интерфейсов, выполненную средствами VBA. Реализация может служить основой для последующих доработок и улучшений.

Проблема функциональных аргументов

Первая проблема, с которой мы столкнемся на этом пути – это проблема передачи функциональных аргументов в функцию или метод. Язык VBA не содержит соответствующих средств (оператор AddressOf служит лишь для передачи адресов функциям Windows API и не вполне безопасен в работе). Это же можно сказать и об известной методике вызова функций по указателю (Магдануров Г.И. Visual Basic на практике СпБ.: “БХВ Петербург”, 2008). Давайте не будем рисковать — используем при реализации только стандартные возможности языка и стандартные библиотеки.

Для передачи функционального объекта в процедуру или функцию язык VBA предлагает стандартную возможность – обернуть нужную функциональность объектной оболочкой (создать объект, одним из методов которого и будет нужная функциональность). К сожалению, здесь нам ООП мало чем поможет. Этот подход работоспособен, однако весьма тяжеловесен – для каждой нужной функциональности придется создавать свой класс и объект этого класса. Объект можно передать как параметр.

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

x -> x+1

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

r=proc(a,b,”x->x+1”)

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

Это выполняет следующая служебная процедура: Чтобы использовать анонимную функцию, заданную подобным образом, ее необходимо сначала привести к стандартному виду функции VBA.


Private Function prepCode(Code As String) As String k% = InStr(Code, "->") parms$ = Trim$(Left$(Code, k% - 1)) body$ = Mid$(Code, k% + 2) If Left$(parms$, 1) <> "(" Then parms$ = "(" + parms$ + ")" If InStr(body$, "self") = 0 Then body$ = ";self=" & body$ & ";" body$ = Replace(body$, ";", vbCrLf) prepCode = "function self" & parms & vbCrLf & body & _ vbCrLf & "end function"
End Function

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


function self(x) self=x+1
End function

Очевидно, что в соответствии с синтаксисом VBA, эта функция будет делать именно то, что должна была делать анонимная функция – увеличивает значение своего аргумента на 1. Правда, эта функция – пока не есть функция VBA, а только строка, содержащая указанный код. Для того, чтобы превратить строку в функцию, можно использовать стандартную майкрософтовскую библиотеку “Msscript.ocx”. Эта COM-библиотека позволяет выполнить произвольный код VBA, заданный в строковой форме. Для этого необходимо выполнить следующее:

— Создать объект ScriptControl
— Вызвать метод установки языка (VBScript);
— Вызвать метод загрузки функции;
— Вызвать метод eval для исполнения вызова.

Все это выглядит примерно так:


Set locEv=new ScriptControl
locEv.Language = "VBScript"
locEv.AddCode prepCode(“x->x+1”)
r=lovEv.eval(“self(5)”)

После выполнения данного кода значение переменной r будет равно 6.

Отдельные операторы в этом случае завершаются точкой с запятой. Здесь следует сделать три замечания:
— Тело анонимной функции может содержать несколько строк. Многострочное тело позволяет реализовывать в анонимных функциях весьма продвинутую функциональность;
— То, что анонимная функция “в действительности” имеет имя “self”, дает неожиданный бонус – анонимная функция может быть рекурсивной.
— Поскольку объект ScriptControl поддерживает два языка – VBScript и Jscript, то безымянная функция может быть (теоретически) написана и на Jscript (но в настоящей работе эта возможность не реализована). Из окончательного кода символы “;” исключаются.

Далее будет описана объектная модель реализации.

Объектная модель

Основой модели являются объекты двух видов: Container и Generator. Объект Container является хранилищем массива произвольных размеров, объект Generator, как следует из названия, реализует генератор общего вида.
Оба объекта реализуют интерфейс aIter, который более подробно описывается ниже. Интерфейс включает 19 функций:
Для объекта-генератора ряд методов впрямую не реализован – необходимо сначала отобрать некоторое количество значений в контейнер. При попытке вызвать для генератора нереализованный метод, генерируется ошибка с кодом 666. Далее будет рассмотрено несколько примеров использования описанных интерфейсов.

Примеры

Печать последовательных чисел Фибоначчи:


Sub Test_1() Dim fibGen As aIter Set fibGen = New Generator fibGen.Init Array(1, 0), "(c,p)->c+p" For i% = 1 To 50 Debug.Print fibGen.getNext() Next i%
End Sub

Здесь создается генератор с начальными значениями 0 и 1 и генерирующей функцией, соответствующей последовательности Фибоначчи. Далее в цикле печатаются первые 50 чисел.
Мапирование и фильтрация:


Sub Test_2() Dim co As aIter
Dim Z As aIter
Dim w As aIter Set co = New Container co.Init frange(1, 100) Set Z = co.map("x -> 1.0/x"). _ take(20).filter(" x -> (x>0.3) or (x<=0.1)") iii% = 1 Do While Z.hasNext() Debug.Print iii%; " "; Z.getNext() iii% = iii% + 1 Loop
End Sub

Создается контейнер и инициализируется числовой последовательностью из диапазона от 1 до 100. Далее числа с помощью map заменяются на обратные. Из них берется двадцать первых. Далее эта совокупность фильтруется и из нее отбираются числа, большие 0.3 или меньшие 0.1. Результат возвращается в контейнере, состав которого распечатывается.
Использование свертки:


Sub Test_4() Dim co As aIter Set co = New Container co.Init frange(1, 100) v = co.reduce(0, "(acc,x)->acc+x") Debug.Print v v = co.reduce(1, "(acc,x)->acc*x") Debug.Print v
End Sub

Здесь с помощью свертки считается сумма и произведение чисел от 1 до 100.


Sub Test_5() Dim co1 As aIter
Dim co2 As aIter
Dim co3 As aIter Set co1 = New Generator co1.Init Array(123456789), "x -> INT(x/10)" Set co2 = co1.takeWhile(100, "x -> x > 0") Set co3 = co2.map("x -> x mod 10") Debug.Print co3.maximun Debug.Print co3.minimum Debug.Print co3.summa Debug.Print co3.production
End Sub

В этом примере строится генератор co1, последовательно делящий исходное число на степени 10. Затем отбираются частные до появления нуля. После чего полученный список частных отображается функцией взятия остатка от деления на 10. В результате получается список разрядов числа. Список суммируется, у него вычисляется максимум, минимум и произведение.

Выводы

Предлагаемый оказывается вполне работоспособным и может быть с успехом применен для решения повседневных задач VBA-программиста в функциональном стиле. Чем мы хуже джавистов?

Скачать примеры можно здесь

Удачи!!!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть