函数式范式与面向对象范式比较 — 以词频统计为例
Posted 区块链技术与应用研究实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数式范式与面向对象范式比较 — 以词频统计为例相关的知识,希望对你有一定的参考价值。
「函数式编程( Functional Programming)范式」与「面向对象( Object Oriented Programming )编程范式」的差异被经常提及。
今天写一篇文章,目的是通过一小段代码,直观地比较这两种范式的差异,希望能给感兴趣的同学们留下一个生动的印象。
注:本文中的 Java 代码来自于 Neal Ford 的 Functional Thinking
任务
输入一个句子,进行分词后先排除掉虚词,然后进行词频统计(不分大小写),最终得到一个单词为 Key,频率为 Value 的字典。
面向对象的 Java 源码
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Words {
private Set<String> NON_WORDS = new HashSet<String>() {{
add("a"); add("an"); add("on");
}}; // 需要忽略的词
public Map wordFreq(String words) {
TreeMap<String, Integer> wordMap = new TreeMap<String, Integer>();
Matcher m = Pattern.compile("\\w+").matcher(words); // 构造正则
while(m.find()) { // 如果还有剩余的词
String word = m.group().toLowerCase(); //取词
if (! NON_WORDS.contains(word)) {
if (wordMap.get(word) == null) {
wordMap.put(word, 1); // 在 wordMap 里还没词的情况下初始化
}else{
wordMap.put(word, wordMap.get(word) + 1);
// 如果有了就 + 1
}
}
}
return wordMap;
}
public static void main(String[] args){ // 主入口
Words words = new Words();
System.out.println(words.wordFreq("A apple on an apple tree"));
}
}
步骤归纳:
构建虚词集合 NON_WORDS
实现 wordFreq 函数
生成空字典 wordMap
遍历单词
取词 + 小写化
首次遇到则初始化
重复遇到则 +1
函数式的 Elixir 源码
defmodule Words do
@moduledoc """
Documentation for Get Words Freq
"""
@non_words ["an", "a", "on"]
def fetch_words_req(words) do
words
|> String.split()
|> reject_non_words()
|> count()
end
def reject_non_words(words_list) do
Enum.reject(words_list, fn word -> reject_non_word(word) end)
end
def reject_non_word(word) do
word
|> String.downcase()
|> Kernel.in(@non_words)
end
defp count(words) when is_list(words) do
Enum.reduce(words, %{}, &update_count/2)
end
defp update_count(word, acc) do
Map.update(acc, String.to_atom(word), 1, &(&1 + 1))
end
end
步骤归纳:
构建虚词合集 @non_words
构造 fetch_words_req 函数
words 进行分词
分词后排除虚词
通过 Enum 遍历,挨个排除
统计词频
遍历单词
首次遇到则初始化
重复遇到则 +1
范式对比
在面向对象范式下,我们看到一个叫做 Words 的机器人,拿起字符串切割成一个个单词,再对单词进行处理,最后得到一个结果。
在函数式范式下,我们看到一个叫做 Words 的车间里的一条叫做 fetch_words_req 的流水线,我们把字符串先通过 split 模块,再通过 reject_non_words 模块,最后通过 count 模块,得到我们想要的结果。
有了对比,我们就可以方便的理解以下问题:
「面向对象范式」与「函数式范式」的差异究竟在哪里?
A:使用「面向对象范式」,我们操纵机器人;使用「函数式范式」,我们操作流水线。
很多教材、资料、文章等会说「OOP = 封装 + 继承 + 多态」。其实这个提法非常的误导人。封装是普遍存在的概念,函数也可以封装。而多态也不限于 OOP ,接口可以多态,用 duck typing 也可以多态。只有继承专属于 OOP ,但是继承只是 OOP 里一个次要一级的代码复用的特性(而且目前还普遍被认为不靠谱,composite over inheritance)。OOP最关键的特征还是通过消息传递来改变 Object 内部的状态。
链接:https://www.zhihu.com/question/19732025/answer/530161703
为什么在过去的开发中面向对象编程占主导?现在函数式火起来了?
A:因为传统方式的确节约性能,在这个例子中只要对句子做一次遍历即可。但是代码的性能和清晰度是一个跷跷板,传统方式用性能换取了清晰度。现在硬件水平提升了,所以可以让代码清晰度占更重的分量,所以——更具有清晰度的函数式编程火起来了。
什么时候选择面向对象?什么时候选择函数式?
A:一个业务领域建模,其实模拟的就是现实当中的不同角色的人/机构的工作方式。因为如果是人/机构互相协作,就是通过消息来协作的。比如博士生想发文章,先得自己写,写了老板审阅,完事发给期刊编辑,编辑找同行评议,完事发表,发表的结果会收录到某个文献索引数据库。这个过程就是多个独立的“对象”在相互协作的结果。因此OOP在这个层面上对这个流程进行抽象是很合适的。当然你也可以说,这时我用FP的各种动作函数的组织来描述这个过程,也是可以的。但是如果比较一下,这个场景用FP和OOP建模,哪个更容易理解呢?
再比如,对一组数据做加工,先查询,然后聚合,聚合后排序,再join,再排序,再聚合,再转换(map)得到最终的结果。这个过程,用FP的函数就很自然。
链接:https://www.zhihu.com/question/19732025/answer/530161703
「函数式、面向对象、命令式、声明式」这四者究竟是什么关系?
命令式编程(Imperative):详细的命令机器怎么(How)去处理一件事情以达到你想要的结果(What);
声明式编程( Declarative):只告诉你想要的结果(What),机器自己摸索过程(How)。
链接:https://zhuanlan.zhihu.com/p/34445114
我个人认为,「命令式」和「面向对象」是一枚硬币的两面;「声明式」和「函数式」是另一枚硬币的两面,欢迎探讨。
后台输入关键字有自动回复:
输入「 刷题 」,推送LeetCode刷题系列;
输入「 比特币 」,推送比特币技术入门教程;
输入「 联盟链 」,推送联盟链开发系列教程;
输入「 项目 」,看看大狗最近在玩什么;
输入「 大狗 」,推送我的个人微信二维码。
以上是关于函数式范式与面向对象范式比较 — 以词频统计为例的主要内容,如果未能解决你的问题,请参考以下文章