SwiftNote-函数式编程

248 阅读3分钟

先从一个示例开始讲起。有这样一个题目:

读取一段长文本,确定所有单词的使用频率并从高到低排序,并打印出所有单词及其使用频率的排序列表。

let text = """
The unforgettable, heartbreaking story of the unlikely friendship between a wealthy boy and the son of his father’s servant, caught in the tragic sweep of history, The Kite Runner transports readers to Afghanistan at a tense and crucial moment of change and destruction. A powerful story of friendship, it is also about the power of reading, the price of betrayal, and the possibility of redemption; and an exploration of the power of fathers over sons—their love, their sacrifices, their lies.
"""
let non_words: Set = ["the", "on", "is", "a", "as", "of", "to", "in", "so", "but"]

// 传统的命令式解决方案
func wordFreq(words: String, non_words: Set<String>) -> [String: Int] {
    // 记录单词及其使用频率的字典
    var wordDict = [String: Int]()
    // 将长文本根据空格切割成单词数组
    let wordList = words.split(separator: " ")
    for word in wordList {
        // 统一转为小写, 方便对比
        let lowerCasedWord = word.lowercased()
        // 过滤不在统计范围内的单词
        if !non_words.contains(lowerCasedWord) {
            
            if let count = wordDict[lowerCasedWord] {
                // 如果出现过, 那么频率 +1
                wordDict[lowerCasedWord] = count + 1
            } else {
                // 如果未出现过, 那么频率设为 1
                wordDict[lowerCasedWord] = 1
            }
        }
    }
    return wordDict
}

// 函数式编程解决方案
func wordFrequency(words: String, non_words: Set<String>) -> [String: Int] {
    var wordDict = [String: Int]()
    let wordList = words.split(separator: " ")
    wordList.map{ $0.lowercased() }
        .filter{ !non_words.contains($0)}
        .forEach{ (word) in
            wordDict[word] = (wordDict[word] ?? 0) + 1
    }
    return wordDict
}

命令式编程风格常常迫使我们处于性能考虑,把不同的任务交织起来,以便能够用依次循环来完成多个任务。

而函数式编程用 map()filter() 这些高阶函数把我们解放出来,让我们站在更高的抽象层次上去思考问题,把问题看的更透彻更清楚。


总结

  • 在面向对象的命令式编程语言里面,重用的单元是类和类之间沟通用的消息。
  • 函数式编程语言实现重用的思路很不一样。函数式语言提倡在有限的几种关键数据结构如(list,set,map)上运用针对这些数据结构高度优化后的操作,以此构成基本的运转机构。开发者再根据具体用途,插入自己的数据结构和高阶函数去调整机构的运转方式。
  • 比起一味创建新的类结构体系,把封装的单元降低到函数级别,更有利于达到细粒度的、基础层面的重用。
  • 函数式程序员细化用少数的几个核心数据结构,围绕它们去建立一套充分优化的运转机构。面向对象程序员喜欢不断地创建新的数据结构和附属操作,因为压倒一切的面向对象编程范式就是建立新的类和类之间的消息。把所有的数据结构都封装成类,一方面压制了方法层面的重用,另一方面鼓励了大力度的框架式的重用。函数式编程的程序构造更方便我们在比较细小的层面上重用代码。

题目 2:假设我们有一个名字列表,其中一些条目的名字是单个字符,现在的任务是将单子字符的名字过滤掉,并且把剩下的名字以逗号分割组成一个字符串返回,且每个名字的首字母大写。

// 传统的命令式解法: 
func cleanNames(names: [String]) -> String {
    var text = ""
    for name in names {
        if name.count > 1 {
            text += name.capitalized + ","
        }
    }
    text.removeLast()
    return text
}

// 函数式解法:
func cleanNames(names: [String]) -> String {
    
    return names.filter{ $0.count > 1 }
                .map{ $0.capitalized }
                .joined(separator: ",")
}

总结

函数式编程将程序描述为表达式和变换,以数学方程的形式建立模型,并且尽量避免可变的状态。函数式编程语言对问题的归类不同于命令式语言。如前面所提到的几种操作(filter、transform、convert、map),每一种都作为一个逻辑分类由不同的函数所代表,这些函数实现了低层次的变换,但依赖与开发者定义的高阶函数作为参数来调整其低层运转机构的运作。