【译】Swift和函数式编程的精髓

我想说这真的是一篇很是很是好的文章,它经过对一个实例的API的优化,教会咱们如何写出优美简洁的Swift的函数式代码。可是这个文章是视频中做者的口述,因此翻译过程当中不免有不当之处。你们能够对着视频和原文进行观看和对比。程序员

原文地址编程

介绍

Swift第一次被公布的一周后,我写了一篇名为“Swift不是函数式”的博文。两年后,Swift仍然不是函数式。这篇文章并无对此进行阐述,而是将讨论函数式语言几十年来的经验和研究,咱们能够用本身快速的方式将这些经验和研究带入Swift。swift

什么是函数式编程

它是单子、仿函数、haskell和优雅的代码。api

函数式编程不是一种语言或语法,而是一种思考问题的方式。函数式编程在咱们有单子以前已经存在了几十年。函数式编程是一种思考如何分解问题,而后以结构化的方式将它们从新组合在一块儿的方法。数组

让咱们从swift中的一个简单示例开始。app

var persons: [Person] = []
for name in names {
    let person = Person(name: name)
    if person.isValid {
        persons.append(person)
    }
}
复制代码

这是一个简单的循环,它作两件事:将name转换成Person,而后将这些人放入一个有效的数组中。很简单,但这里有不少事情。为了理解发生了什么,你须要在头脑中执行这个。你须要思考,“这个数组是作什么的?“函数式编程

它看起来很简单,但可能会更简单。咱们能够把问题分开。咱们能够把东西拆开。 咱们如今有两个循环,每一个循环作的更少。每个都很简单,更简单的代码让咱们找到模式:建立一个数组,遍历一些值,对每一个值执行一些操做,而后在最后将它们放到另外一个数组中。函数

当你有一些事情作了不少次,你可能应该提取函数-Swift有一个,它被称为mapmap将某物的列表转换为其余某物的列表。重要的是它说明了它的含义:可能personsnamesperson的映射。这是一份根据人名列出的名单。咱们没必要在头脑中执行任何东西,他就在代码中表述咱们的意思。工具

let possiblePersons = names.map(Person.init)
let persons = possiblePersons.filter { $0.isValid }
复制代码

另外一个循环也是一个很是常见的模式:它有一个名为“filter”的方法。filter接受一个谓词,它是一个返回bool的函数。它使用函数给咱们返回有效的数据。咱们能够把这些结合在一块儿,从而得到possiblePersons,而后把他们加入咱们的过滤器。post

从七行代码到两行代码。另外,咱们能够从新组合它们:这些都是咱们能够从新组合起来的值。咱们用链式把他们进行组合。

它很容易阅读,咱们能够一行一行地阅读。

let persons = names
    .map(Person.init)
    .filter { $0.isValid }
复制代码

它十分易学而且很容易适应这种方式。在这样的例子中你没必要再写一个for循环了。

函数式工具

1977年,John Backus(协助发明了Fortran 和 Algol)得到了图灵奖,发表演讲“编程能从冯诺依曼风格中解放出来吗?“我喜欢这个标题。“冯·诺依曼风格”是指Fortran和Algol。

这篇论文是他为发明它们而作的声明。他表示命令式编程,一步一步地改变某种状态,直到得到你想要的最终状态。当他说“函数式”的时候,那并非指咱们今天所说的函数式,但他启发了许多函数式研究人员去研究和学习。

这篇论文引发了个人兴趣,咱们能够回到swift:咱们如何将复杂的事情分解成简单的事情。把这些简单的东西泛化,而后用一些规则把它们粘在一块儿,好比代数。

代数是一套规则,用来把事物组合在一块儿,把它们拆开,而后转换它们。咱们能够想出能够用来操纵程序的规则。咱们已经作到了:咱们作了一个循环,咱们把它分解成两个简单的循环,找到每个循环的通用形式,而后使用链式将它们从新组合在一块儿。当haskell程序员第一次遇到swift时,他们每每会感到沮丧,由于试着作它们在Haskell语言中作的事。

在haskell中,与几乎全部的函数式语言同样,组成的基本单元是函数。有不少漂亮的方法来组合函数。我能够经过将foldr函数和+函数粘合在一块儿,并将其初始值设为0,来建立一个名为sum的新函数。无论你读起来是否舒服,只要你这样作了,它就至关漂亮了。

let sum = foldr (+) 0
  sum [1..10]
复制代码

你能够用swift来作,可是它会很难看,并且它不能很好地工做,由于你在用错误单元组合它们。swift不是函数式。

swift中的组成单位是类型。类、结构、枚举和协议,这些都是能够组合的。咱们一般把两种类型粘在一块儿。经过实现一个函数并将它们粘在一块儿,咱们能够用更简单的片断来构建它们。

swift中另外一种很是常见的构图是将类型放在语境中。你最经常使用的是optionals。可选类型是根据语境肯定的,在语境中可能有值也可能没有值。这是一个与类型相关的小信息:它是否存在?这就是语境的含义。添加语境比其余跟踪额外信息的方法强大得多。

extension MyStruct<T>: Sequence {
    func makeIterator() -> AnyIterator<T> {
return ... }
}
复制代码

咱们能够跟踪一个事实,即不存在整数,或者根本不存在值,这是由于咱们会从-1这样的整数中窃取一个值,这意味着没有值。可是如今你必须处处测试,这是丑陋的,容易出错的,编译器不能帮你。

若是咱们将整数改成可选的,这时编译器能够帮助您。你有这样的语境“它存在吗,它不存在吗,我能够帮助你确保你不会忘记这一点。”若是你曾经使用过-1,很容易忘记检查,而后你的程序变得不可控了。

例子

让咱们创建一个更复杂的例子。

func login(username: String, password: String, completion: (String?, Error?) -> Void)
login(username: "rob", password: "s3cret") {
    (token, error) in
    if let token = token {
        // success
    } else if let error = error {
// failure }
}
复制代码

这是一个很是常见的API。咱们有一个带有usernamepassword参数的login函数,在某个时刻,它将返回一个token和一个可能的错误。我认为咱们能够经过考虑语境作得更好。

第一个问题 是这个completion。我说它是一个string,是什么样的string。她是一个token.我能够给它贴个标签,但那没用。在swift中,标签不是类型的一部分。即便我这么作了,仍是这个string。字符串能够是指不少东西。

Tokens有规则:例如,它们可能必须是固定长度,或者不能为空。这些都是能够用字符串作的,但不能用tokens。咱们但愿有更多关于token的上下文;它有规则,因此咱们但愿捕获这些规则。咱们能够作到,咱们能够给它更多的结构。这就是为何它被称为struct

struct Token {
    let string: String
}
复制代码

我把字符串放入结构体中。这不会花费任何成本,也不会致使间接或任何额外的内存使用,但如今我能够对此设置规则。

你只能用特定的字符串来构造它们。我能够在上面加上一些扩展,这样就没有必要用任意的字符串了。这要好多了,我能够对全部类型都这样作;字符串固然没问题,也能够是字典、数组和整数类型。

当您拥有这些类型时,您能够将它们提高到上下文中,并控制能够放在它们上的内容。你能够控制他表达的意思。我不须要标签或注释,由于第一个参数显然是一个token,由于它的类型是Token

第二个问题 是咱们要传递usernamepassword。在大多数有这些的程序中,您老是将它们一块儿传递;password自己尤为无用。我想建立一个规则,容许我用“and”组合用户名和密码,因此我须要一个“and”类型。咱们有一个,它又是一个结构。

"AND"类型

struct Credential {
  var username: String
  var password: String
}
复制代码

结构体是"and"类型。 Credential 是由usernamepassword组成. “and”类型被称为 “生产类型”. 我鼓励你大声说出来。例如:“凭证是用户名和密码。”这有意义吗?若是它没有意义,也许它是错误的类型,或者你建立它是错误的。

func login(credential: Credential, completion: (Token?, Error?) -> Void)


let credential = Credential(username: "rob",
                            password: "s3cret")
login(credential: credential) { (token, error) in
    if let token = token {
        // success
    } else if let error = error {
// failure }
}
复制代码

如今咱们能够交换Credential,而不是usernamepassword:这也使得咱们的签名更短更清晰。咱们还提供了许多不错的可能性:在Credentials上添加扩展,在其余类型的规则下交换它们。也许咱们想要10次password,或者access token,或者facebook或者google等等。如今,我不须要更改代码的任何其余部分,由于我只是传递credentials

不过,它也有问题。咱们经过了元组(Token?, Error?) –元组是“and”类型。它们是匿名结构。咱们的意思是“也许是token,也许是error”?有四种可能性:二者都有,或者二者都没有,或者一个或另外一个。只有两种可能性是有意义的。若是我获得了一个tokenerror?这是一个错误条件吗?我须要一个致命的错误吗?我须要忽略它吗?你须要考虑一下这个问题,并可能针对它编写测试。

“OR” Type (Sum)

问题是你不是说“也许”什么的-你是指一个token或一个error。咱们有可使用的“或”类型吗?

enum Result<Value> {
    case success(Value)
    case failure(Error)
}
复制代码

It is an enum - enums are “or” types (this or that), whereas structs are “and” types (this and that). Like “and” types are called “product types”, “or” types are called “sum types”. 这是一个枚举 - enums是 “or”类型,就像结构体是“and”类型。正如“and”类型被称做“生产类型”,“or”类型被称为“和类型”

func login(credential: Credential, completion: (Result<Token>) -> Void)
login(credential: credential) { result in
    switch result {
    case .success(let token): // success
    case .failure(let error): // failure
    }
}
复制代码

我想创建这个result类型。这让我很困扰,由于它不是内置在swift中的。它很容易建造。咱们将提高咱们的值,给它更多的语境。它从一种值转变为一个成功的值。

咱们的错误变成一个失败的错误,咱们有更多的语境。若是咱们将result、生成的token扔进咱们的api中,那么咱们必须针对全部这些状况编写测试来保证全部不可能的状况都会消失。咱们没必要担忧他们,由于他们是不可能的。我但愿错误不可能,而不是编写测试用例。

我喜欢这个API。我用credential登陆,它会给我一个生成的token

这个课程

这是函数式编程的真正精髓,也是咱们应该带给swift的:复杂的事情能够分解成更小、更简单的事情。

咱们能够为这些简单的事情找到通用的解决方案,咱们可使用一致的规则将这些简单的事情从新组合起来,让咱们对咱们的程序进行推理。这使得编译器更容易查出bug,我认为70年代的John Backus彻底赞成这一点。把它拆了,把它造起来。

相关文章
相关标签/搜索