函数式编程思想及其解释器的实现

来源:互联网 发布:linux配置网卡 编辑:程序博客网 时间:2024/06/05 06:47

前言

函数表达式在Excel中有着非常重要的作用,excel的公式就是一种基于函数的表达式,但公式中支持诸如“+”、“-”等运算符。纯函数表达式应该只包含函数和参数。遵循这种思想我们可以将数学上的中缀表达式”(56-9)*3+1/6”使用函数的方式来表示:

ADD(MULTIPLY(MINUS(56,9),3),DIVIDE(1,6))

即每一种运算符都可以抽象为一个函数(比较类似于前缀表达式),这就是函数式编程思想。在函数式编程中,函数起到了决定性的作用,目前有很多著名的函数式编程语言(如LISP、F#),这里我要使用C#实现一个自己的函数式编程语言,在本文中,我将只实现“+”、“-”、“*”、“/”四个操作符函数。

基本思想

我们知道,每种语言都具有它自身的语法,我在这里实现的语言(我这里将其命名为PureF)的语法非常简单,它由函数名、数值、“(”、“)”、“,”这几种元素构成,于是我可以用Token来表示每种元素,后面我还将实现其Tokenizer(分词器)。下图为Token结构体:

对于上面例子中的函数表达式,我们可以构建它的AST(抽象语法树):


从图中可以看到,一个函数的参数也可以是一个函数,通过这种嵌套性,我们可以实现一些复杂的运算。

通过对语言元素进行文法分析,我们需要建立语言中存在的两种表达式:函数表达式、值表达式,函数表达式表示了一个函数的函数名及其参数列表,而值表达式则表示一个数值(我这里只先实现整数类型)。

使用这两种表达式,我们就可以建立语法树了。

在得到语法树后,触发根元素的计算方法,通过递归可以将所有参数计算为数值,最终求解。

解释器实现

首先解释几个概念:

1.      Context(上下文)

顾名思义,它管理了整个翻译过程中所涉及的变量(包括全局和局部)、函数、堆栈、状态等,相当于一个容器。我们可以将一些自定义函数添加到上下文中,上下文是我们对语言进行扩展的入口。

2.      Tokenization(分词操作)

一个表达式将许多语言元素混在一起,对人来说可以非常容易地理解,而对于计算机,我们需要通过程序来对一个表达式进行分割,以便于后续的工作

3.      AST(语法树)

语法树可以表示一个具体的解释过程,计算将从树根开始,遇到函数节点将继续调用它的计算方法,这就相当于从树的最高级开始计算直到树根,最后将整个表达式计算完毕。

 

然后我们建立表示表达式的抽象类AbstractExpression及实现的整数表达式和函数表达式:


这里着重看一下函数表达式的实现:


从上面的图中我们可以看出Context的作用,表达式节点从Context中获取相应函数,并将参数代入执行。

 

接下来实现分词器,这里的分词器比较简单,我写的相对弱一些,至少可以看懂,思路就是建立一个临时可拼接字符串,普通字符直接压入字符串中,遇到左括号就将上一个字符串压入Tokens列表中,然后压入一个左括号标记,再清除上面的临时字符串。遇到右括号和逗号也是同理,这里要裁剪掉空白符,贴出代码:


最后如果临时字符串中还有非空白字符,也将其压入Tokens中


开始的例子中的那个表达式经过分词后将得到:

ADD  (  MULTIPLY (  MINUS  (  56  ,  9  )  ,  3  )  ,  DIVIDE  (  1  ,  6  )  )

 

然后用这些tokens,我们就可以开始分法分析了,也就是构建语法树。

简述一下思路:建立两个栈(分别是函数栈和参数栈),遇到数值压入参数栈,遇到左括号将上一token压入函数栈,遇到右括号先将上一个token压入函数栈顶(Peek操作)的函数,再将函数压入参数栈,遇到逗号则是将参数栈顶的参数压入函数栈顶的函数。最后将从参数栈中Pop得到根函数。

 

代码:


 

这里演示一下表达式”ADD(MULTIPLY(5,7),6)”的构建过程(F表示函数栈,A表示参数栈):

1.      ADD

F: (Empty)

A: (Empty)

2.      (

F: ADD

A: (Empty)

3.      MULTIPLY

F: ADD

A: (Empty)

4.      (

F: ADD MULTIPLY

A: (Empty)

5.      5

F: ADD MULTIPLY

A: 5

6.      ,

F: ADD MULTIPLY(5)

A: (Empty)

7.      7

F: ADD MULTIPLY(5)

A: 7

8.      )

F: ADD

A: MULTIPLY(5,7)

9.      ,

F: ADD(MULTIPLY(5,7))

A: (Empty)

10.  6

F: ADD(MULTIPLY(5,7))

A: 6

11.  )

F: (Empty)

A: ADD(MULTIPLY(5,7),6)

 

A栈中就有已经构建好的语法树。

 

触发跟函数的计算方法即可,至此,整个解释过程结束。

 

扩展解释器

现在实现的解释器所说可以构建出语法树,但是还不能计算出数值,因为我们还没有每种运算符的函数实现,所以我在这里实现了四则运算的函数:


 

然后使用委托,将函数进行封装,保存在Context的一个字典中,以供使用。


 

执行测试

将上面实现的方法封装成一个类,然后在主函数中构建测试:


 

测试几个表达式:


 

由于没有实现表示浮点的表达式,因此所有无法整除的数都会截掉小数部分。


完整的源码Available now:Github


得意

3 0
原创粉丝点击