作者自述CSE语言设计思想(四)----用CSE模拟LISP语言(中)
2011-12-26 22:01
337 查看
first-class函数
在计算机科学中,一门拥有first-class函数的语言,是要求将函数看成first-class对象,具体来说,这意味着函数既可以作为参数传递给其它函数,也可以从其它函数返回,还可以将它赋值给一个变量,或保存到一个列表数据中。first-class可译为“首类”,它最早由Christopher Strachey在一篇“函数作为一等公民”(“functions as first-class citizens”)中提及,但这个词现在有点用滥了,甚至以讹传讹,网上不少文章都说“FP编程”是一等公民了。
前面我们举例的loop函数就表现了“first-class函数”特性,下面进一步举例CSE函数的“自身数据”与“计算值”含义。
“@0”是CSE中一个比较特殊的操作符,“@0”含义为取表达式自身,“@1”是将表达式计算一次,“@2”计算两次,“@N”计算N次。上面我们取“$(print,3)”表达式自身赋给变量vFunc。然后计算“vFunc@1”我们将得到vFunc变量的值(即“$(print,3)”表达式自身),计算“vFunc@2”,print函数才被调用。
把上面print替换成lambda,我们会看到一个有趣的应用:
把“用lambda定义一个匿名函数”的语句自身,赋给vFunc,然后使用它:
我们定义test函数,传入参数为v,如果这么调用:
您将看到结果值是4。因为test函数执行时,在计算“v@ 2”时CSE系统才用“$(lambda,i,$(+,i,1))”动态的定义一个函数,然后传入3调用它,3 + 1结果是4。
把上面的变量vFunc与test去掉,作等作替换,写成如下样子:
运行看看,计算结果是否为4?
让FP编程更加自然
有点晕了吧?FP编程的特点与人的思维习惯不是很吻合,我们常说,3加上4(即“3+4”),但不说“加上:3,4”(即“$(+,3,4)”),我们容易理解:先定义一个某某函数,再用这个函数调用,但用FP风格描述是:“调用:定义一个函数,传入参数”,很容易让人糊涂。
现在我用命令式风格,把上面语句复述一遍:
第一条语句是定义一个test函数,第二条是调用test函数,传入参数是lambda定义匿名函数的表达式,注意:是表达式自身,而不是该表达式的计算结果(一个匿名函数)。
上一篇我们介绍过操作运算与函数调用具有等价性,为方便大家以FP风格使用CSE,我推荐大家这么写代码:
而不宜写成这样:
后一写法没错,但未迎合人们思考习惯,前一写法更加自然,且在CSE语言环境中,仍算作合格的FP编码风格。
还记得上一篇介绍过CSE的FP与Common Lisp的差异吗?CL用空格分隔各参数,CSE用逗号,CL用括号,CSE用“$()”,看似CSE繁琐一点,但换来上述更直观的表达形式。像“test(vFunc)”在CL中是非法的,CSE则用它表示常规函数调用。
高阶函数
Highter-order function是与First-order function相对存在的一个概念,前者是我们常说的高阶函数,后者我们可理解为普通函数。为澄清这个概念,我摘录wikipedia.org上的定义:
In mathematics and computer science, higher-orderfunctions, functional forms, or functionals are functions which do at least oneof the following:
1. take oneor more functions as an input
2. output a function
All other functions are first-order functions.
意思是说,以一个(或多个)函数用作传入参数,或者返回值是函数的函数,是Highter-orderfunction,所有其它称为first-orderfunctions。
除了前面已举例的几个例子,下面我再补充介绍FP编程中常用的reduce、filter、currying调用。
reduce用于依次运算参数列表,比如定义一个两数相加操作,计算[1,2,3,4]的过程为:
我们先用CSE定义reduce函数:
然后调用:
计算结果为10,定义reduce函数与前面举列过的loop类似,每次取前两个数值做运算,然后如果有下一次循环,就返回下轮reduce调用的算式,否则借用PASS调用返回结果值。
filter函数通过传入一个过滤条件,将指定列表的数据筛选一遍。
这么调用filter:
该调用的结果值为[1,2,3]。
curry也是众多FP语言支持的函数,它能依次组装函数参数,我们先看一下这个函数怎么用:
我们用CSE的class类定义curry:
利用operator()重载函数调用操作,每次函数调用传入一个参数后,都记录到args类成员中,最后一次不带参数调用时(即使用缺省参数CseNull),系统才构造运算表达式实施调用。
试验一下上述定义:
看看结果值是不是18。curry函数具有分散组装函数参数的功能,可用来构造某种并行计算机制。另外还有一种需求,为简化书写或维持某种一致的接口需要,我们可能要隐藏特定函数参数,这时,在CSE中可改用更简便的“列表调用”方式去实现。比如有一个remoteEval函数提供远程计算功能,要求传入两个参数:指定远程机器、指定待计算的表达式,常规调用如下:
当大量存在这种让MachineA实现计算的调用时,我们改用如下方式更简洁些:
这里remoteEval是我们假设的函数,还不存在,我们改用print演示一下这种用法:
打印输出分别是“log>It is so good!”与“err> It seems something wrong”。
(未完,待续)
相关文章:
作者自述CSE语言设计思想(三)----用CSE模拟LISP语言(上)
作者自述CSE语言设计思想(五)----用CSE模拟LISP语言(下)
在计算机科学中,一门拥有first-class函数的语言,是要求将函数看成first-class对象,具体来说,这意味着函数既可以作为参数传递给其它函数,也可以从其它函数返回,还可以将它赋值给一个变量,或保存到一个列表数据中。first-class可译为“首类”,它最早由Christopher Strachey在一篇“函数作为一等公民”(“functions as first-class citizens”)中提及,但这个词现在有点用滥了,甚至以讹传讹,网上不少文章都说“FP编程”是一等公民了。
前面我们举例的loop函数就表现了“first-class函数”特性,下面进一步举例CSE函数的“自身数据”与“计算值”含义。
$(as,vFunc,$(print,3)@0)
“@0”是CSE中一个比较特殊的操作符,“@0”含义为取表达式自身,“@1”是将表达式计算一次,“@2”计算两次,“@N”计算N次。上面我们取“$(print,3)”表达式自身赋给变量vFunc。然后计算“vFunc@1”我们将得到vFunc变量的值(即“$(print,3)”表达式自身),计算“vFunc@2”,print函数才被调用。
把上面print替换成lambda,我们会看到一个有趣的应用:
$(as,vFunc,$(lambda,i,$(+,i,1))@0)
把“用lambda定义一个匿名函数”的语句自身,赋给vFunc,然后使用它:
$(as,test, $(lambda,v,$(v@2,3)) )
我们定义test函数,传入参数为v,如果这么调用:
$(test,vFunc)
您将看到结果值是4。因为test函数执行时,在计算“v@ 2”时CSE系统才用“$(lambda,i,$(+,i,1))”动态的定义一个函数,然后传入3调用它,3 + 1结果是4。
把上面的变量vFunc与test去掉,作等作替换,写成如下样子:
$($(lambda,v,$(v@2,3)),$(lambda,i,$(+,i,1))@0)
运行看看,计算结果是否为4?
让FP编程更加自然
有点晕了吧?FP编程的特点与人的思维习惯不是很吻合,我们常说,3加上4(即“3+4”),但不说“加上:3,4”(即“$(+,3,4)”),我们容易理解:先定义一个某某函数,再用这个函数调用,但用FP风格描述是:“调用:定义一个函数,传入参数”,很容易让人糊涂。
现在我用命令式风格,把上面语句复述一遍:
testas lambda: v, return v@2 (3); end; test( lambda: i, return i + 1; end @ 0 );
第一条语句是定义一个test函数,第二条是调用test函数,传入参数是lambda定义匿名函数的表达式,注意:是表达式自身,而不是该表达式的计算结果(一个匿名函数)。
上一篇我们介绍过操作运算与函数调用具有等价性,为方便大家以FP风格使用CSE,我推荐大家这么写代码:
3+ 4 return 3 + 4 test(vFunc)
而不宜写成这样:
$(+,3,4) $(return,$(+,3,4)) $(test,vFunc)
后一写法没错,但未迎合人们思考习惯,前一写法更加自然,且在CSE语言环境中,仍算作合格的FP编码风格。
还记得上一篇介绍过CSE的FP与Common Lisp的差异吗?CL用空格分隔各参数,CSE用逗号,CL用括号,CSE用“$()”,看似CSE繁琐一点,但换来上述更直观的表达形式。像“test(vFunc)”在CL中是非法的,CSE则用它表示常规函数调用。
高阶函数
Highter-order function是与First-order function相对存在的一个概念,前者是我们常说的高阶函数,后者我们可理解为普通函数。为澄清这个概念,我摘录wikipedia.org上的定义:
In mathematics and computer science, higher-orderfunctions, functional forms, or functionals are functions which do at least oneof the following:
1. take oneor more functions as an input
2. output a function
All other functions are first-order functions.
意思是说,以一个(或多个)函数用作传入参数,或者返回值是函数的函数,是Highter-orderfunction,所有其它称为first-orderfunctions。
除了前面已举例的几个例子,下面我再补充介绍FP编程中常用的reduce、filter、currying调用。
reduce用于依次运算参数列表,比如定义一个两数相加操作,计算[1,2,3,4]的过程为:
((1 + 2) + 3 ) + 4
我们先用CSE定义reduce函数:
reduceas lambda: declare(Func,v,#_); switch( #_.len(), @(#_.insert(0,Func(v,(#_.pop(0)) @-1)); #_.insert(0,Func); #_.insert(0,reduce@0); #_.insert(0,`$()`@0); setRet(#notator(#_)); ); setRet(#notator(PASS@0,v)); ); end;
然后调用:
$($(reduce,lambda:v1,v2, v1 + v2 end,1,2,3,4))
计算结果为10,定义reduce函数与前面举列过的loop类似,每次取前两个数值做运算,然后如果有下一次循环,就返回下轮reduce调用的算式,否则借用PASS调用返回结果值。
filter函数通过传入一个过滤条件,将指定列表的数据筛选一遍。
filteras lambda: declare(Func,bList); i as bList.len(); $($(loop, --i >= 0, Func(bList[i]) or bList.pop(i); )); return bList; end;
这么调用filter:
filter(lambda:i, i <= 3 end,[1,2,3,4,5])
该调用的结果值为[1,2,3]。
curry也是众多FP语言支持的函数,它能依次组装函数参数,我们先看一下这个函数怎么用:
addingas lambda: i1,i2,i3, i1 + i2 + i3; end; fill_0 as curry(adding); fill_1 as fill_0(5); fill_2 as fill_1(6); fill_3 as fill_2(7); result as fill_3();
我们用CSE的class类定义curry:
classcurry: declare args as TObjArray; declare fun as TCseObj; func curry(me,vFunc): me.fun = vFunc; me.args = []; end; func `operator()`(me,vArg=CseNull) asTCseObj: if CseNull != vArg: me.args.append(vArg); return me; end else: bFunc as [me.fun]; bFunc.join(me.args); return #notator(bFunc) @ 2; end; end; end;
利用operator()重载函数调用操作,每次函数调用传入一个参数后,都记录到args类成员中,最后一次不带参数调用时(即使用缺省参数CseNull),系统才构造运算表达式实施调用。
试验一下上述定义:
curry(lambda: i1,i2,i3, i1 + i2 + i3; end)(5)(6)(7)()
看看结果值是不是18。curry函数具有分散组装函数参数的功能,可用来构造某种并行计算机制。另外还有一种需求,为简化书写或维持某种一致的接口需要,我们可能要隐藏特定函数参数,这时,在CSE中可改用更简便的“列表调用”方式去实现。比如有一个remoteEval函数提供远程计算功能,要求传入两个参数:指定远程机器、指定待计算的表达式,常规调用如下:
remoteEval(MachineA,sExpression);
当大量存在这种让MachineA实现计算的调用时,我们改用如下方式更简洁些:
machineA_evalas [remoteEval,MachineA]; machineA_eval("dir()"); machineA_eval("3 + 5");
这里remoteEval是我们假设的函数,还不存在,我们改用print演示一下这种用法:
logMsgas [print,"log>"]; logMsg("It is so good!"); errMsg as [print,"err>"]; errMsg("It seems something wrong");
打印输出分别是“log>It is so good!”与“err> It seems something wrong”。
(未完,待续)
相关文章:
作者自述CSE语言设计思想(三)----用CSE模拟LISP语言(上)
作者自述CSE语言设计思想(五)----用CSE模拟LISP语言(下)
相关文章推荐
- 作者自述CSE语言设计思想(五)----用CSE模拟LISP语言(下)
- 作者自述CSE语言设计思想(三)----用CSE模拟LISP语言(上)
- 作者自述CSE语言设计思想(八)----CSE-Super语言设计思路(中)
- 作者自述CSE语言设计思想(一)----做50号语言,还是0号语言?
- 作者自述CSE语言设计思想(九)---- CSE-Super语言设计思路(下)
- 作者自述CSE语言设计思想(六)----基于HTML5跨越NativeApp与WebApp的一种途径
- 作者自述CSE语言设计思想(七)----CSE-Super语言设计思路(上)
- 作者自述CSE语言设计思想(二)----CSE语言表达风格
- LavaX语言的设计思想
- 各语言设计思想的独特之处:C/C++、Java、Python、Objective C、Groovy
- 《Java程序员修炼之道》作者Ben Evans:保守的设计思想是Java的最大优势
- 各语言设计思想的独特之处:C/C++、Java、Python、Objective C、Groovy
- Silverlight开发动态多国语言支持(本地化)的网页游戏设计思想
- java语言讲解singleton的编程思想---深入浅出单实例Singleton设计模式
- 对于java程序语言的单例设计模式讲解
- 转:Java之面向对象思想设计原则
- 小博老师解析设计思想 ——七大设计原则
- 关注设计上的意义,而不是实现细节,从今天开始更多的关注思想
- nodejs 设计思想杂记四 异步控制流模式
- 从面向对象到关系型数据的设计(一) 是什么束缚了我们的思想