Swift中的泛型与函数编程
下面的两个求和函数变体是我试图重复Abelson和Sussman在Swift的经典《计算机程序的结构和解释》一书中介绍的lisp版本。第一个版本用于计算一个范围内的整数和,或一个范围内整数的平方和,第二个版本用于计算pi/8的近似值 我无法将这些版本合并为一个func,该func将处理所有类型。有没有一种聪明的方法可以使用泛型或其他一些Swift语言特性来组合变体Swift中的泛型与函数编程,swift,functional-programming,generics,Swift,Functional Programming,Generics,下面的两个求和函数变体是我试图重复Abelson和Sussman在Swift的经典《计算机程序的结构和解释》一书中介绍的lisp版本。第一个版本用于计算一个范围内的整数和,或一个范围内整数的平方和,第二个版本用于计算pi/8的近似值 我无法将这些版本合并为一个func,该func将处理所有类型。有没有一种聪明的方法可以使用泛型或其他一些Swift语言特性来组合变体 func sum(term:(Int)->Int,a:Int,next:(Int)->Int,b:Int)->Int{ 如果a>b{
func sum(term:(Int)->Int,a:Int,next:(Int)->Int,b:Int)->Int{
如果a>b{
返回0
}
回报(期限(a)+总和(期限,下一个(a),下一个,b))
}
func sum(term:(Int)->Float,a:Int,next:(Int)->Int,b:Int)->Float{
如果a>b{
返回0
}
回报(期限(a)+总和(期限,下一个(a),下一个,b))
}
与
sum({$0},1,{$0+1},3)
6因此
sum({$0*$0},3,{$0+1},4)
结果是25
8.0*和({1.0/浮点数($0*($0+2))},1,{$0+4},2500)
3.14079因此为了使它更容易一些,我稍微更改了方法签名,并假设它足以处理
函数和(term:(T->T),a:T,next:(T->T),b:T)->T{
,其中T是某种数字
不幸的是,Swift中没有编号,所以我们需要自己制作。我们的类型需要支持
- 加成
- 比较
(用于添加的中性元素)0
compariable
协议中处理,对于重置,我们可以创建自己的协议:
protocol NeutralAdditionElementProvider {
class func neutralAdditionElement () -> Self
}
protocol Addable {
func + (lhs: Self, rhs: Self) -> Self
}
sum
实现
我们现在可以实现sum
功能:
func sum <T where T:Addable, T:NeutralAdditionElementProvider, T:Comparable> (term: (T -> T), a: T, next: (T -> T), b: T) -> T {
if a > b {
return T.neutralAdditionElement()
}
return term(a) + sum(term, next(a), next, b)
}
提供中性元素:
extension Int: NeutralAdditionElementProvider {
static func neutralAdditionElement() -> Int {
return 0
}
}
extension Double: NeutralAdditionElementProvider {
static func neutralAdditionElement() -> Double {
return 0.0
}
}
Sebastian回答了您的问题(+1),但可能还有其他利用现有通用函数的简单函数解决方案(如
reduce
,它经常用于sum
类计算)。也许:
let π = [Int](0 ..< 625)
.map { Double($0 * 4 + 1) }
.reduce(0.0) { return $0 + (8.0 / ($1 * ($1 + 2.0))) }
你会这样称呼它:
let π = apply(Double(0.0), { return $0 + 8.0 / Double((Double($1) * Double($1 + 2))) }, 1, { $0 + 4}, 2500)
注意,它不再是一个sum
函数,因为它实际上只是一个“从a
到b
重复执行此闭包”,因此我选择将其重命名为apply
但是正如您所看到的,我们已经将加法移到了传递给这个函数的闭包中。但是它让您摆脱了必须将每个要传递给“generic”函数的数字类型重新定义为可添加的愚蠢
注意,这也解决了Sebastian为您解决的另一个问题,即需要为每种数字数据类型实现neutralAdditionElement
。这对于执行您提供的代码的直译是必要的(即,如果a>b
,则返回零)但是我改变了循环,当a>b
时,它不会返回零,而是在a==b
(或更大)时返回计算出的项
值
现在,这个新的泛型函数可以用于任何数字类型,而不需要实现任何特殊函数或使它们符合任何特殊协议。坦率地说,这里还有改进的余地(我可能会考虑使用范围
或序列类型
或类似的东西),但它涉及到了您最初的问题,即如何使用泛型函数来计算一系列计算表达式的和。要使其以真正泛型的方式运行,答案可能是“从泛型函数中取出+
,并将其移到闭包中”。有一个很好的方法使其“起作用”对于一个简单的测试用例。然而,正如所指出的,类似于可添加的
协议的东西并不完全符合函数式编程(或者通常是Swift)的精神。这些解决方案都是可行的,因此这个答案将更多地涉及“精神”问题
让我们讲述四个操作员的故事:
func +(lhs: Int, rhs: Int) -> Int
func +(lhs: Int32, rhs: Int32) -> Int32
func +(lhs: Float, rhs: Float) -> Float
func +(lhs: String, rhs: String) -> String // See note below
其中哪一个具有相同的行为?“很明显,前三个是相同的,”你可能会说,“最后一个是不同的:1+1=2
,不管你是处理Int
还是Int32
,这当然意味着与1.0+1.0=2.0
相同的事情。但是“1”+“1=”11
,因此添加字符串是完全不同的
这些假设是不正确的-所有四个操作员都有不同的行为
当您添加1
和2_147_483_647
时会发生什么情况?(现在让我们忽略字符串
的情况-我想我们都同意字符串串联不同于数字加法。)这取决于这些值的类型,也可能取决于其他因素
- 如果两者都是
…那么,您是为32位还是64位CPU编译的?Int
在32位上被别名为Int
,在64位上被别名为Int32
。刚刚得到一台闪亮的新iPhone 6?太好了,您的答案是Int64
2_147_483_648
- 如果两者都是
(或者如果编译为32位)…那么,Int32
是可能的最大有符号32位整数,因此向其中添加一个会产生溢出。在Swift中,使用2_147_483_647
操作符添加会导致溢出,因此会崩溃。Swift会迫使您考虑是否需要溢出行为(通过选择+
或+
运算符)这样您就不会&+
- 如果两者都是
,则会得到Float
。但是,等等,什么是2.14748365E+9
?这也是Float(2_147_483_648)
-添加一个没有任何作用!IEEE 32位浮点格式有24位尾数,因此如果添加两个相差超过2^24的数字,则没有足够的精度来存储结果2.14748365E+9
func apply<T, U: Comparable>(initial: T, term: (T, U) -> T, a: U, next: (U) -> U, b: U) -> T { let value = term(initial, a) if a < b { return apply(value, term, next(a), next, b) } else { return value } }
let π = apply(Double(0.0), { return $0 + 8.0 / Double((Double($1) * Double($1 + 2))) }, 1, { $0 + 4}, 2500)
func +(lhs: Int, rhs: Int) -> Int func +(lhs: Int32, rhs: Int32) -> Int32 func +(lhs: Float, rhs: Float) -> Float func +(lhs: String, rhs: String) -> String // See note below