作者自述CSE语言设计思想(四)----用CSE模拟LISP语言(中)

本文深入探讨了CSE语言中的first-class函数特性,包括如何将函数作为参数传递、从其他函数返回以及赋值给变量。通过具体实例展示了函数如何与自身数据和计算相结合,以及如何使用lambda函数实现匿名函数的动态定义与调用。同时介绍了高阶函数的概念,如reduce、filter和currying,并提供了相应的代码示例。文章旨在帮助读者更好地理解和运用CSE语言的高级编程技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

first-class函数

在计算机科学中,一门拥有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语言(下)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值