本文将介绍96. 通用字段修改器用到的在LotusScript中模拟函数式编程的技巧。
函数式编程
函数式编程是一种优美而强大的编程范式。它源于Alonzo Church提出的λ演算(Lambda演算),而某个问题能表示成Lambda演算,按照Church–Turing论题,等价于该问题在数学上是可以有效计算的。粗略地说,用对应于Lambda演算的一门函数式程序语言可以写出任何理论上可计算问题的计算程序。因为与可计算理论的紧密关系和强大的表现力,函数式编程在学术界历来很受重视。但是在日常应用的软件产业,它的声名和流行程度就远不及命令式和面向对象的编程范式。不过近年来随着Erlang、Clojure等函数式编程语言的崭露头角,以及JavaScript、Scala、Python等混合范式编程语言中函数式编程的应用,函数式编程越来越受到业界主流的注意和推广。微软在.Net平台上推出了F#,Java也引进了Lambda表达式。
与一般程序员熟悉的命令式编程相比,函数式编程有以下几个主要特色:
- 函数是一级(first-class)对象。意味着函数与其他普通数据类型的值一样,能够被赋予变量,作为参数传给其他函数,作函数的返回值。
- 尽量避免函数的副作用。即某个函数运算用到的所有外部数据都以参数传入,结果以返回值传出,而不读取和修改外部公共变量。
- 更多使用递归,少用循环。
- 往往涉及大量的列表(list)计算。
下面用既能进行命令式编程又能进行函数式编程的JavaScript的一个简单例子来演示两者之间的差别。把一个包含数字的数组中的每个元素变成其平方。
命令式:
var list=[1, 2, 3, 4, 5];
function square(source){
for (i=0; i<source.length; i++)
{
source[i]*=source[i]
}
}
square(list);
函数式:
var list=[1, 2, 3, 4, 5];
list=list.map(
function(elem){
return elem*elem;
}
);
习惯于命令式编程的人起初或许会对函数式编程的思路和表达方式感到有些古怪和不适应,但一旦熟悉了,就会喜欢它的方便简捷和强大。比如上面的例子,JavaScript数组的map方法是函数式编程语言列表对象常有的工具,功能是返回一个新的列表,其中每个元素都是将原列表的对应元素传入参数中的函数得到的返回值。这样省去了命令式编程中每当需要处理列表中元素所需的循环套路,只需给出核心的“业务逻辑”——求平方的算法。实际上即使在传统的命令式编程语言中,也常常有传递某个函数的需要。事件式编程的核心就是在事件发布者和订阅者之间传递事件处理程序,或者换个术语回调函数。C有函数指针,.Net平台的语言有代理,Java程序的最小单元是类,所以为了传递一个函数也只能将其包装在一个类中。
在LotusScript中模拟函数式编程
LotusScript中的函数也不能独立地传递,为了模拟,我们只能利用作为一种脚本语言它可以在程序中将一段字符串解释执行的能力。在为自定义对象模拟事件时,我们已经应用了这一技巧。这里我们再用它来模拟函数式编程。
‘Operate on an element in an array. Used by ArrayMap.
Private Function Operate(obj As Variant, op As String)
‘Operates on an object
Execute_Access=obj
Dim pre As String, post As String, pos As Integer
If Not StrContains(op,"(") Then
op=op & "()"
End If
Dim pairs
pairs=Split("():(,:,):,,",":")
Dim pair As String
ForAll p In pairs
pair=CStr(p)
If StrContains(op,pair) Then
pos=InStr(op,pair)
pre=Left(op,pos)
post=Mid(op,pos+1)
Exit ForAll
End If
End ForAll
If pre><"" Then
Dim script As String
script={Execute_Access=} & pre & {Execute_Access} & post
‘script="Execute_Access=StrRight(Execute_Access,"""")"
‘Stop
Execute(script)
Operate=Execute_Access
End If
End Function
在上面这个函数中,我们把作为参数传入的op解释成一个函数,应用在另一个参数obj上。为了使op还能包含它对应的函数本身的参数,如传入{StrRight(,|”|)}字符串对应StrRight函数和|”|参数,Operate对op可能包含的括号和逗号做了一些处理。有了Operate函数做基础,我们就可以模拟上述的map方法:
Function ArrayMap(arr As Variant, op As String) As Variant
‘operates on each element in an array
If IsArray(arr) Then
Dim result() As Variant
Dim lb As Integer, ub As Integer
lb=LBound(arr)
ub=UBound(arr)
ReDim result(lb To ub)
Dim i As Integer
For i=lb To ub
result(i)=Operate(arr(i), op)
Next
ArrayMap=result
End If
End Function
在96. 通用字段修改器给出的代码末端,我们看到频繁使用ArrayMap函数,这种风格使得对数组元素的多次处理显得逻辑清晰,如果改用传统的循环来编写,不仅代码会长很多,也更难于理解和维护。