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{

下面的两个求和函数变体是我试图重复Abelson和Sussman在Swift的经典《计算机程序的结构和解释》一书中介绍的lisp版本。第一个版本用于计算一个范围内的整数和,或一个范围内整数的平方和,第二个版本用于计算pi/8的近似值

我无法将这些版本合并为一个func,该func将处理所有类型。有没有一种聪明的方法可以使用泛型或其他一些Swift语言特性来组合变体

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
时会发生什么情况?(现在让我们忽略
字符串
的情况-我想我们都同意字符串串联不同于数字加法。)这取决于这些值的类型,也可能取决于其他因素

  • 如果两者都是
    Int
    …那么,您是为32位还是64位CPU编译的?
    Int
    在32位上被别名为
    Int32
    ,在64位上被别名为
    Int64
    。刚刚得到一台闪亮的新iPhone 6?太好了,您的答案是
    2_147_483_648
  • 如果两者都是
    Int32
    (或者如果编译为32位)…那么,
    2_147_483_647
    是可能的最大有符号32位整数,因此向其中添加一个会产生溢出。在Swift中,使用
    +
    操作符添加会导致溢出,因此会崩溃。Swift会迫使您考虑是否需要溢出行为(通过选择
    +
    &+
    运算符)这样您就不会
  • 如果两者都是
    Float
    ,则会得到
    2.14748365E+9
    。但是,等等,什么是
    Float(2_147_483_648)
    ?这也是
    2.14748365E+9
    -添加一个没有任何作用!IEEE 32位浮点格式有24位尾数,因此如果添加两个相差超过2^24的数字,则没有足够的精度来存储结果
    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