Swift中的功能设计模式:解释器

Swift与Java,C#和Objective-C不同。 这些都是出现在面向对象编程热潮高峰期的语言。 特别是Java和C#总是试图将您的解决方案压缩到面向对象的框架中。 自由功能不应该存在。 他们应该是某个班级的一部分。

从那时起,整个软件开发社区变得更加务实,并接受有时考虑功能而不是对象是有用的。 诸如Scala,Clojure,Swift,Kotlin和Julia之类的较新的编程语言都更加注重功能结构,而以OOP为代价。

最近,我读了Reza Shirazian关于Swift中的设计模式的博客,特别是解释器模式。 他展示了如何以Java和C#中的经典OOP传统实现设计模式。

我在这里想要做的是显示Swift中还有其他可用的方法。 这是功能性思维而非面向对象思维的演示。 这个想法是您尝试将问题组合成功能而不是对象的组合。

Reza的示例正在创建一个程序,该程序可以解析简单的数学表达式,例如:

  l + p-10.00 

我们将不讨论如何从文本字符串创建解析树。 相反,我们将研究如何构建和评估表示该表达式的解析树。

因此,我们想要做的是创建一个抽象语法树,如上图所示,它表示我们的数学表达式。 Reza很好地介绍了这一点,因此我不再重复。 取而代之的是,我将研究如何在语法树中表示各个节点,以及如何将其组成以形成树以及如何评估该树。

下面是组成几个节点以形成这样的语法树的示例。

 让expression =减去(add(variable(“ l”),variable(“ p”)),number(10.0)) 

然后,我们可以使用绑定到变量l和p的值来评估它。

  var结果= exp([“ l”:2.0,“ p”:4.0]) 

在Reza的“面向对象”方法中,像“添加”节点这样的单个节点被表示为一个对象:

 类添加:表达式{ 

var leftOperand:表达式
var rightOperand:表达式

init(leftOperand:Expression,rightOperand:Expression){
self.leftOperand = leftOperand
self.rightOperand = rightOperand
}

func interpret(变量:[String:Expression])-> Double {
返回leftOperand.interpret(variables)+ rightOperand.interpret(variables)
}
}

他重复此模式来描述其他运算符,例如减法,除法和乘法。

表达式表示为协议:

 协议表达式{ 
func interpret(变量:[String:Expression])-> Double
}

功能方法

在我们的示例中,我们将使用函数。 因此,我们将表达式定义为一个函数,该函数接受将字符串映射为双精度值并返回双精度值的字典。

  typealias Expression =([[String:Double])-> Double 

首先我们要用数字文字表示

  func number(value:Double)-> Expression { 
返回{_
返回值
}
}

我们正在创建一个函数号,该函数号返回另一个函数(或更具体地讲,闭包)。 返回的函数为Expression类型,这意味着我们可以通过传递变量及其绑定值的字典来对其进行评估。

我们可以用类似的方式制作添加节点:

  func add(leftOperand:Expression,rightOperand:Expression)-> Expression { 
返回{
返回leftOperand(variables)+ rightOperand(variables)
}
}

但是,正如我们从Reza的示例中看到的那样,它变成了许多看起来相同的样板代码。 对于每个节点,代码中的唯一区别是运算符分别从+切换到-,*和/。

因此,我们将转而接受功能性思维,并创建一个使表达功能起作用的功能。

因此,我们正在制作一个函数,该函数返回带有此签名的函数:

  (表情,表情)->表情 

+,-,*和/都是二进制运算符。 因此,在每种情况下,我们都将两个表达式组合在一起以创建一个新的表达式。

我们还需要一种方法来传递在每个表达式中执行的二进制运算符。 这将由另一个类型别名表示:

  typealias BinaryOperator =(双精度,双精度)->双精度 

因此+,-,*和/都符合此描述。 例如

  3 + 4 

可以认为是:

  +(3,4) 

所以这里是out函数,它创建二进制表达式:

  func binaryExpression(op:BinaryOperator)->(((Expression,Expression)-> Expression){ 
返回{leftOperand,rightOperand in
return {(变量:[String:Double])-> Double in
op(leftOperand(variables),rightOperand(variables))
}
}
}

让我们剖析这意味着什么。 我们返回一个带有左右表达式的函数。

 返回{leftOperand,rightOperand in 
}

此函数使用这些操作数创建一个新表达式。 一个表达式恰好是一个在其操作数上执行二进制运算符的函数:

  return {(变量:[String:Double])-> Double in 
op(leftOperand(variables),rightOperand(variables))
}

如果我们以常规函数语法编写此代码,则它将看起来像:

  func binary(变量:[String:Double])-> Double { 
op(leftOperand(variables),rightOperand(variables))
}

因此,现在我们有了一种制作所有二进制运算符节点的快速方法:

 让我们添加= binaryExpression(+) 
让减法= binaryExpression(-)
让乘法= binaryExpression(*)
让除法= binaryExpression(/)

最后,我们必须有一个变量节点:

  func变量(名称:字符串)->表达式{ 
返回{
变量[名称] 0.0
}
}

现在我们可以结合所有这些来创建语法树并对其进行评估:

  let变量= [“ l”:2.0,“ p”:4.0] 

让exp =减去(add(variable(“ l”),variable(“ p”)),number(10.0))
exp(变量)

可能需要一点时间来习惯于组合返回函数和返回函数的函数。 但是正如您所看到的,这是一种非常强大的代码构建方式,这使得可以在相对较少的代码行中创建许多功能。