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

消除边际效应

当我们使用变量记录运算过程中用到的数据时,就引入了边际效应的风险,举一个简单例子:

bData as [1,2,3,4,5,6,7];
## do something
for i in range(bData.len()-1,-1,-1):
  if bData[i] > 3:
    bData.pop(i);
  end;
end;
## do something
print(bData);

这段代码用于把bData列表中数据筛选一遍,把不符合条件的数据(i > 3)删掉,然后打印结果数据。

看上去很简单,不容易出错,但如果程序写复杂,代码量增加了,比如上面代码中用“do something”注释的地方,插入大量代码,bData变量可能不经意被修改,有时修改不在本处,可能用参数传递到其它函数被修改了,结果print值并非预期,这种情况就是边际效应(side-effects)。

实现同样过滤功能,我们可改用如下方式编写无副作用的代码:

bData as lambda: vCond,bInput, filter(vCond,bInput); end;
print(bData(lambda: i, i <= 3 end,[1,2,3,4,5,6,7]));

这里,bData同样是变量,但它描述一种运算,即:利用FP的惰性求值的特性,保障特定数据免遭意外被改。

 

Bindings捆绑

Binding不只是FP语言的一个特色,也是脚本语言的通用特色。在脚本语言中,一个变量总是以“实体”的形式存在的,C/C++不是这样,比如:

int i = 5;
i = 5.5f;

这里变量i始终是int类型,尽管第二条语句将float值赋给它,系统把值转化为int再赋过去。相应的,脚本语言处理此类语句更向改换链接(变量名只是一个链接标识),比如在Python中:

i = 5
i = 5.5

第一条语句执行后,i是整形变量,而第二条语句执行后i变成浮点变量了。我们可以把变量i看作一种标识,该标识先与值为5的整数实体捆绑,然后当第二语句执行后,改成与值为5.5的浮点数实体捆绑。

这就是强类型与弱类型语言的差别。我们看一下CSE语言如何处理的:

i as TInt = 5;
i = 5.5f;

CSE解释器也像C/C++那样,执行第二条语句时把浮点数转成int类型再赋过去,这种设计迎合了“用脚本仿真C/C++”的需求,与常见脚本语言处理风格存在很大差异。

接下来,我们要用CSE的Interface风格的类定义模拟Binding特性,因为CSE的class类也缺省去仿真C++的class了,Interface风格类提供一种自定义操作的机制,CSE已用它模拟Python脚本接口,请参阅CSE在线帮助《PyLib参考手册》。

我们先定义一个Binding类,如下:

class Binding: end as AInterface;

class Binding:
  declare #dict as TEntryArray*;

  func Binding(me):
    ppDict as TEntryArray** = &me asTEntryArray**;
    *ppDict = new TEntryArray();
  end;

  func `~Binding`(me):
    ppDict as TEntryArray** = &me asTEntryArray**;
    delete *ppDict;
    *ppDict = NULL;
  end;

  func `#=`:  ## dir()
    declare(me);
    return me.#dict->keys();
  end;

  func `#1:`: ## get attribute
    declare(me,attr);
    if "#dict" == attr:
      return *(&me as TEntryArray**);
    end else:
      pDict as *(&me asTEntryArray**);
      ret as pDict->get(attr.toId());
      if CseNull == ret:
        throw(EAttrError,"attribute(%s) inexistent" % [attr]);
      end else return ret;
    end;
  end;

  func `#1;`: ## set attribute
    declare(me,attr,value);
    if "#dict" == attr:
      throw(EAttrError,"attribute(#dict) is readonly");
    end else:
      pDict as *(&me asTEntryArray**);
      pDict->set(attr.toId(),value);
      return value;
    end;
  end;
end;

这个Binding类重新定义存取类成员的方式,都定向到对#dict成员读写,#dict是字典类型,众多“key-value”成对保存在该变量中。然后,我们运行如下代码:

ASet as Binding();
ASet.i = 5;
ASet.i = 5.5f;

前一条对ASet.i赋值,i变量是int整形,后一条对ASet.i赋值,i变量将变为浮点值,即:通过Binding类,我们让CSE也缺省提供弱类型赋值。

删除ASet.i变量:

ASet.i = CseNull;

查看ASet下都定义了哪些实体:

dir(ASet);

再验证一下函数实体是否也支持:

ASet.test = lambda: i1,i2, i1 + i2; end;
ASet.test(3,5);

ASet.test(3,5)调用结果肯定是8。

 

Closures闭包

Closure是一种将函数与它依赖的运行环境一起打包的技术,它最初是在20世纪60年代作为Scheme组成部分开发的,目前JavaScript、Python、Ruby、PHP(V5.3以后版本)都支持闭包。

闭包的价值通常建立在lambda匿名函数之上,有了闭包,不仅函数自身作为一种数据可以被传递,被记录,运行该函数的上下文环境也被保存,这让经过一次或多次传递后的函数仍能正常调用。

比如,我们有如下代码:

iPeriod as TInt = 7;

func nextPeriodFunc() as TCseObj:
  return lambda: i, iPeriod + i end;
end;

一周是7天,全局变量iPeriod值为7,已知本周一是2号,求下周一是多少号?

nextWeek as nextPeriodFunc();
nextWeek(2);

这种编程风格容易产生边际效应,全局变量不小心被改将导致nextWeek运算出错,改用闭包形式:

func nextPeriodFunc(iDay) as TCseObj:
  iPeriod as TInt = iDay;
  return Closure(lambda: i, iPeriod + i end);
end;

nextWeek as nextPeriodFunc(7);
nextWeek(2);

把iPeriod变量改作局部变量,然后使用闭包,把“lambda: i, iPeriod+ i end”函数定义与它运行的环境(含iPeriod)打包。最后调用nextWeek(2),您将得到结果值:9。

CSE已提供将函数环境打包(#packSpace)及闭包调用(#closureCall)的功能,我们用如下代码封装一个Closure类就可以了:

class Closure:
  declare space as TCseObj;
  declare fun as TCseObj;

  func Closure(me,AFunc,Space =#packSpace()):
    me.fun = AFunc;
    me.space = Space;
  end;

  func `operator()`:
    declare(me,#_);
    return #closureCall(me.fun,#_,me.space);
  end;
end;

需要提醒一下,闭包特性延伸了函数内局部变量的生存周期,如上面举例iPeriod局部变量并不随nextPeriodFunc(iDay)函数调用结束而释放,如果这个nextPeriodFunc还定义另一个占用大量内存的变量,结果是什么?它占用的内存将一直占着,直到闭包实体被释放(如上面例子中nextWeek变量被删除)。

 

CSE与函数式编程

CSE语言具备完整支持函数式编程的能力,但不意味着它现在就是一门函数式编程语言,本文介绍了CSE在FP编程通用特性上的实现情况,但CSE官方并未正式宣称CSE支持FP编程(其实正式宣称也未尝不可,业界大部分支持FP的语言并非纯粹只用函数式表达风格)。

CSE用户手册未正式推荐FP特性,主要因为当前状况下,还不存在必须用FP才能解决的应用,FP的表述风格终归不如命令式那么好学、易用、也更人性。或许,CSE今后向多核并行方向发展时,FP编程才正式推荐给大家使用。

本系列文章(用CSE模拟LISP语言)用到的代码已整理到functional.cse文件,请点击此处下载


(完)


相关文章:

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

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值