VBA中的功能接口...

“ ...那些不愿意盯着业余者公开谴责傻瓜的人,让他们观察一下我如何证明Java和Visual Basic在出生时是双胞胎分开的,而C ++他们甚至不是远亲。”

布鲁斯·麦金尼(Bruce McKinney)“ Die Hard Basic Basic”

引言


当前,对功能编程方法的持续关注导致了这样一个事实,即传统的编程语言正在积极地获取功能性手段。 尽管纯功能语言仍然不是很流行,但是该功能已在诸如C ++,Java,JavaScript,Python等语言中牢固建立,但是VBA语言已经在相当多的Microsoft Office用户中流行了很多年。这种语言实际上不包含任何功能性工具。

让我们尝试填补这一空白-我提出由VBA实现的功能接口的完整(尽管可能不是完美的)实现。 实施可以作为后续改进和改进的基础。

功能参数问题


我们将沿着这条路径遇到的第一个问题是将函数参数传递给函数或方法的问题。 VBA语言不包含适当的工具(AddressOf运算符仅用于将地址传输到Windows API函数,并且使用起来并不完全安全)。 对于众所周知的通过指针调用函数的方法也可以这么说(G. Magdanurov Visual Basic在实践中的圣彼得堡:“ BHV Petersburg”,2008)。 不要冒险-我们在实现中仅使用标准语言功能和标准库。

不幸的是,这里的PLO并没有太大帮助。 要将功能对象转移到过程或函数中,VBA提供了一个标准机会-用对象外壳包装必要的功能(创建对象,其中一种方法将是必要的功能)。 可以将对象作为参数传递。 这种方法是可行的,但是非常繁琐-对于每种必需的功能,您都必须创建自己的类和该类的对象。

还有另一种方法,它简单得多,不需要为每个功能创建单独的类。

假设您要将匿名函数传递给某个proc过程,该过程会将其参数加1。 该函数可以编写如下:

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函数,而只是包含指定代码的一行。 为了将字符串转换为函数,可以使用标准的Microsoft库“ Msscript.ocx”。 该COM库允许您执行以字符串形式指定的任意VBA代码。 为此,请执行以下操作:

-创建一个ScriptControl对象
-调用语言安装方法(VBScript);
-调用函数加载方法;
-调用eval方法进行调用。

一切看起来像这样:

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

执行此代码后,变量r的值为6。

这里应该指出三点:

  • 匿名函数的主体可以包含几行。 在这种情况下,个别语句以分号结尾。 在最终代码中,字符“;” 被排除在外。 多行主体允许您在匿名函数中实现非常高级的功能;
  • 匿名函数“实际上”具有名称“ self”的事实带来了意想不到的好处-匿名函数可以递归。
  • 由于ScriptControl对象支持两种语言-VBScript和Jscript,因此匿名函数可以(理论上)用Jscript编写(希望的人可以尝试)。

接下来,将描述对象实现模型。

对象模型


该模型的基础是两种类型的对象:容器和生成器。 Container对象是一个任意大小的数组的存储库,顾名思义,Generator对象实现了一个通用的表单生成器。

这两个对象都实现了aIter接口,下面将对其进行详细说明。 该界面包括19个功能:

方法名称参量结果
isGen--如果对象是生成器,则返回True。
isCont--如果对象是容器,则返回True。
getCont--对于容器,返回本地数组,对于生成器,返回Empty
getNext--返回以下值
hasNext--如果存在以下值,则返回True
初始化iniVal作为变量,lambda作为String =“”,emptyC作为Boolean = FalseiniVal-初始值;
lambda-生成器的匿名函数
emptyC-设置为True时,将创建一个空容器
n为整数返回包含从源对象获得的n个连续值的容器
筛选条件lambda作为字符串返回通过根据未命名的lambda函数过滤原始对象而获得的对象
地图lambda作为字符串返回通过根据未命名的lambda函数映射原始对象而获得的对象
减少acc作为变体,lambda作为字符串,返回当前对象与初始电池值acc和lambda参数指定的折叠函数的卷积结果
采取n作为整数,
lambda作为字符串
返回一个包含n个(或更少个)连续值的容器,这些值满足未命名的lambda函数指定的谓词
dropWhilen作为整数,
lambda作为字符串
返回一个容器,该容器在跳过满足lambda指定的谓词的值之后,包含n个(或更少)从原始值获得的连续值。
拉链作为一个Iter,
n作为整数= 10
它接受一个容器或生成器,并从基本容器和容器容器返回包含值对的容器。 默认结果大小为十。
邮编作为一个Iter,
lambda作为String,
n作为整数= 10
具有两个参数的容器和匿名函数。 返回一个容器,其中包含将指定函数应用于连续对的结果-一个来自基础容器的值,另一个来自参数容器的值。
尺码--对于容器,返回元素数
摘要--容器值总和
生产量--集装箱价值的乘积
最大值--容器中的最大值
最低--容器中的最小值

对于生成器对象,没有直接实现许多方法-必须首先在容器中选择一定数量的值。 当尝试为生成器调用未实现的方法时,代码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程序员的日常任务。 为什么我们比Javists差?

在此处下载示例

祝你好运!

Source: https://habr.com/ru/post/zh-CN455347/


All Articles