蚊章汇 - 函数式编程
type: channel
link: https://linuxcn.bwsl.wang/tag/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B/
items: Java 中的数据流和函数式编程
items: 使用 Python 的 toolz 库开始函数式编程
items: Python 函数式编程：不可变数据结构
items: JavaScript 函数式编程介绍
items: 函数式编程简介

Java 中的数据流和函数式编程
type: item
date: 2020-02-06T08:25:00+08:00
description: 学习如何使用 Java 8 中的流 API 和函数式编程结构。当 Java SE 8（又名核心 Java 8）在 2014 年被推出时，它引入了一些更改，从根本上影响了用它进行的编程。这些更改中有两个紧密相连的部分：流 API 和函数式编程构造。本文使用代码示例，从基础到高级特性，介绍每个部分并说明它们之间的相互作用。基础特性流 API 是在数据序列中迭代元素的简洁而高级的方法。包 java.util.stream 和 java.util.function 包含了用于流 API 和相关函数式编程构造的新库。当然，代码示例胜过千言万语。下面的代码段用大约 2,000 个随机整数值填充了一个 List：Random rand = new Random2(); List<Integer> list = new ArrayList<Integer>(); // 空 list for (int i = 0; i < 2048; i++) list.add(rand.nextInt()); // 填充它另外用一个 for 循环可用于遍历填充列表，以将偶数值收集到另一个列表中。流 API 提供了一种更简洁的方法来执行此操作：List <Integer> evens = list .stream() // 流化 list .filter(n -> (n & 0x1) == 0) // 过滤出奇数值 .collect(Collectors.toList()); // 收集偶数值这个例子有三个来自流 API 的函数：stream 函数可以将集合转换为流，而流是一个每次可访问一个值的传送带。流化是惰性的（因此也是高效的），因为值是根据需要产生的，而不是一次性产生的。filter 函数确定哪些流的值（如果有的话）通过了处理管道中的下一个阶段，即 collect 阶段。filter 函数是 高阶的 higher-order ，因为它的参数是一个函数 —— 在这个例子中是一个 lambda 表达式，它是一个未命名的函数，并且是 Java 新的函数式编程结构的核心。lambda 语法与传统的 Java 完全不同：n -> (n & 0x1) == 0箭头（一个减号后面紧跟着一个大于号）将左边的参数列表与右边的函数体分隔开。参数 n 虽未明确类型，但也可以明确。在任何情况下，编译器都会发现 n 是个 Integer。如果有多个参数，这些参数将被括在括号中，并用逗号分隔。在本例中，函数体检查一个整数的最低位（最右）是否为零，这用来表示偶数。过滤器应返回一个布尔值。尽管可以，但该函数的主体中没有显式的 return。如果主体没有显式的 return，则主体的最后一个表达式即是返回值。在这个例子中，主体按照 lambda 编程的思想编写，由一个简单的布尔表达式 (n & 0x1) == 0 组成。collect 函数将偶数值收集到引用为 evens 的列表中。如下例所示，collect 函数是线程安全的，因此，即使在多个线程之间共享了过滤操作，该函数也可以正常工作。方便的功能和轻松实现多线程在生产环境中，数据流的源可能是文件或网络连接。为了学习流 API, Java 提供了诸如 IntStream 这样的类型，它可以用各种类型的元素生成流。这里有一个 IntStream 的例子：IntStream // 整型流 .range(1, 2048) // 生成此范围内的整型流 .parallel() // 为多个线程分区数据 .filter(i -> ((i & 0x1) > 0)) // 奇偶校验 - 只允许奇数通过 .forEach(System.out::println); // 打印每个值IntStream 类型包括一个 range 函数，该函数在指定的范围内生成一个整数值流，在本例中，以 1 为增量，从 1 递增到 2048。parallel 函数自动划分该工作到多个线程中，在各个线程中进行过滤和打印。（线程数通常与主机系统上的 CPU 数量匹配。）函数 forEach 参数是一个方法引用，在本例中是对封装在 System.out 中的 println 方法的引用，方法输出类型为 PrintStream。方法和构造器引用的语法将在稍后讨论。由于具有多线程，因此整数值整体上以任意顺序打印，但在给定线程中是按顺序打印的。例如，如果线程 T1 打印 409 和 411，那么 T1 将按照顺序 409-411 打印，但是其它某个线程可能会预先打印 2045。parallel 调用后面的线程是并发执行的，因此它们的输出顺序是不确定的。map/reduce 模式map/reduce 模式在处理大型数据集方面变得很流行。一个 map/reduce 宏操作由两个微操作构成。首…

使用 Python 的 toolz 库开始函数式编程
type: item
date: 2018-11-15T17:42:09+08:00
description: toolz 库允许你操作函数，使其更容易理解，更容易测试代码。在这个由两部分组成的系列文章的第二部分中，我们将继续探索如何将函数式编程方法中的好想法引入到 Python中，以实现两全其美。在上一篇文章中，我们介绍了不可变数据结构。 这些数据结构使得我们可以编写“纯”函数，或者说是没有副作用的函数，仅仅接受一些参数并返回结果，同时保持良好的性能。在这篇文章中，我们使用 toolz 库来构建。 这个库具有操作此类函数的函数，并且它们在纯函数中表现得特别好。 在函数式编程世界中，它们通常被称为“高阶函数”，因为它们将函数作为参数，将函数作为结果返回。让我们从这里开始：def add_one_word(words, word): return words.set(words.get(word, 0) + 1)这个函数假设它的第一个参数是一个不可变的类似字典的对象，它返回一个新的类似字典的在相关位置递增的对象：这就是一个简单的频率计数器。但是，只有将它应用于单词流并做归纳时才有用。 我们可以使用内置模块 functools 中的归纳器。functools.reduce(function, stream, initializer)我们想要一个函数，应用于流，并且能能返回频率计数。我们首先使用 toolz.curry 函数：add_all_words = curry(functools.reduce, add_one_word)使用此版本，我们需要提供初始化程序。但是，我们不能只将 pyrsistent.m 函数添加到 curry 函数中； 因为这个顺序是错误的。add_all_words_flipped = flip(add_all_words)flip 这个高阶函数返回一个调用原始函数的函数，并且翻转参数顺序。get_all_words = add_all_words_flipped(pyrsistent.m())我们利用 flip 自动调整其参数的特性给它一个初始值：一个空字典。现在我们可以执行 get_all_words(word_stream) 这个函数来获取频率字典。 但是，我们如何获得一个单词流呢？ Python 文件是按行供流的。def to_words(lines): for line in lines: yield from line.split()在单独测试每个函数后，我们可以将它们组合在一起：words_from_file = toolz.compose(get_all_words, to_words)在这种情况下，组合只是使两个函数很容易阅读：首先将文件的行流应用于 to_words，然后将 get_all_words 应用于 to_words 的结果。 但是文字上读起来似乎与代码执行相反。当我们开始认真对待可组合性时，这很重要。有时可以将代码编写为一个单元序列，单独测试每个单元，最后将它们全部组合。如果有几个组合元素时，组合的顺序可能就很难理解。toolz 库借用了 Unix 命令行的做法，并使用 pipe 作为执行相同操作的函数，但顺序相反。words_from_file = toolz.pipe(to_words, get_all_words)现在读起来更直观了：将输入传递到 to_words，并将结果传递给 get_all_words。 在命令行上，等效写法如下所示：$ cat files | to_words | get_all_wordstoolz 库允许我们操作函数，切片、分割和组合，以使我们的代码更容易理解和测试。via: https://opensource.com/article/18/10/functional-programming-python-toolz作者：Moshe Zadka 选题：lujun9972 译者：Flowsnow 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

Python 函数式编程：不可变数据结构
type: item
date: 2018-11-12T06:41:16+08:00
description: 不可变性可以帮助我们更好地理解我们的代码。下面我将讲述如何在不牺牲性能的条件下来实现它。在这个由两篇文章构成的系列中，我将讨论如何将函数式编程方法论中的思想引入至 Python 中，来充分发挥这两个领域的优势。本文（也就是第一篇文章）中，我们将探讨不可变数据结构的优势。第二部分会探讨如何在 toolz 库的帮助下，用 Python 实现高层次的函数式编程理念。为什么要用函数式编程？因为变化的东西更难推理。如果你已经确信变化会带来麻烦，那很棒。如果你还没有被说服，在文章结束时，你会明白这一点的。我们从思考正方形和矩形开始。如果我们抛开实现细节，单从接口的角度考虑，正方形是矩形的子类吗？子类的定义基于里氏替换原则。一个子类必须能够完成超类所做的一切。如何为矩形定义接口？from zope.interface import Interface class IRectangle(Interface): def get_length(self): """正方形能做到""" def get_width(self): """正方形能做到""" def set_dimensions(self, length, width): """啊哦"""如果我们这么定义，那正方形就不能成为矩形的子类：如果长度和宽度不等，它就无法对 set_dimensions 方法做出响应。另一种方法，是选择将矩形做成不可变对象。class IRectangle(Interface): def get_length(self): """正方形能做到""" def get_width(self): """正方形能做到""" def with_dimensions(self, length, width): """返回一个新矩形"""现在，我们可以将正方形视为矩形了。在调用 with_dimensions 时，它可以返回一个新的矩形（它不一定是个正方形），但它本身并没有变，依然是一个正方形。这似乎像是个学术问题 —— 直到我们认为正方形和矩形可以在某种意义上看做一个容器的侧面。在理解了这个例子以后，我们会处理更传统的容器，以解决更现实的案例。比如，考虑一下随机存取数组。我们现在有 ISquare 和 IRectangle，而且 ISequere 是 IRectangle 的子类。我们希望把矩形放进随机存取数组中：class IArrayOfRectangles(Interface): def get_element(self, i): """返回一个矩形""" def set_element(self, i, rectangle): """'rectangle' 可以是任意 IRectangle 对象"""我们同样希望把正方形放进随机存取数组：class IArrayOfSquare(Interface): def get_element(self, i): """返回一个正方形""" def set_element(self, i, square): """'square' 可以是任意 ISquare 对象"""尽管 ISquare 是 IRectangle 的子集，但没有任何一个数组可以同时实现 IArrayOfSquare 和 IArrayOfRectangle.为什么不能呢？假设 bucket 实现了这两个类的功能。>>> rectangle = make_rectangle(3, 4) >>> bucket.set_element(0, rectangle) # 这是 IArrayOfRectangle 中的合法操作 >>> thing = bucket.get_element(0) # IArrayOfSquare 要求 thing 必须是一个正方形 >>> assert thing.height == thing.width Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError无法同时实现这两类功能，意味着这两个类无法构成继承关系，即使 ISquare 是 IRectangle 的子类。问题来自 set_element 方法：如果我们实现一个只读的数组，那 IArrayOfSquare 就可以是 IArrayOfRectangle 的子类了。在可变的 IRectangle 和可变的 IArrayOf* 接口中，可变性都会使得对类型和子类的思考变得更加困难 —— 放弃变换的能力，意味着我们的直觉所希望的类型间关系能够成立了。可变性还会带来作用域方面的影响。当一个共享对象被两个地方的代码改变时，这种问题就会发生。一个经典的例子是两个线程同时改变一个…

JavaScript 函数式编程介绍
type: item
date: 2017-10-26T16:30:00+08:00
description: 探索函数式编程，通过它让你的程序更具有可读性和易于调试当 Brendan Eich 在 1995 年创造 JavaScript 时，他原本打算将 Scheme 移植到浏览器里 。Scheme 作为 Lisp 的方言，是一种函数式编程语言。而当 Eich 被告知新的语言应该是一种可以与 Java 相比的脚本语言后，他最终确立了一种拥有 C 风格语法的语言（也和 Java 一样），但将函数视作一等公民。而 Java 直到版本 8 才从技术上将函数视为一等公民，虽然你可以用匿名类来模拟它。这个特性允许 JavaScript 通过函数式范式编程。JavaScript 是一个多范式语言，允许你自由地混合和使用面向对象式、过程式和函数式的编程范式。最近，函数式编程越来越火热。在诸如 Angular 和 React 这样的框架中，通过使用不可变数据结构可以切实提高性能。不可变是函数式编程的核心原则，它以及纯函数使得编写和调试程序变得更加容易。使用函数来代替程序的循环可以提高程序的可读性并使它更加优雅。总之，函数式编程拥有很多优点。什么不是函数式编程在讨论什么是函数式编程前，让我们先排除那些不属于函数式编程的东西。实际上它们是你需要丢弃的语言组件（再见，老朋友）：循环：whiledo...whileforfor...offor...in用 var 或者 let 来声明变量没有返回值的函数改变对象的属性 (比如: o.x = 5;)改变数组本身的方法：copyWithinfillpoppushreverseshiftsortspliceunshift改变映射本身的方法：cleardeleteset改变集合本身的方法：addcleardelete脱离这些特性应该如何编写程序呢？这是我们将在后面探索的问题。纯函数你的程序中包含函数不一定意味着你正在进行函数式编程。函数式范式将 纯函数 pure function 和 非纯函数 impure function 区分开。鼓励你编写纯函数。纯函数必须满足下面的两个属性：引用透明：函数在传入相同的参数后永远返回相同的返回值。这意味着该函数不依赖于任何可变状态。无副作用：函数不能导致任何副作用。副作用可能包括 I/O（比如向终端或者日志文件写入），改变一个不可变的对象，对变量重新赋值等等。我们来看一些例子。首先，multiply 就是一个纯函数的例子，它在传入相同的参数后永远返回相同的返回值，并且不会导致副作用。function multiply(a, b) { return a * b; } 下面是非纯函数的例子。canRide 函数依赖捕获的 heightRequirement 变量。被捕获的变量不一定导致一个函数是非纯函数，除非它是一个可变的变量（或者可以被重新赋值）。这种情况下使用 let 来声明这个变量，意味着可以对它重新赋值。multiply 函数是非纯函数，因为它会导致在 console 上输出。let heightRequirement = 46; // Impure because it relies on a mutable (reassignable) variable. function canRide(height) { return height >= heightRequirement; } // Impure because it causes a side-effect by logging to the console. function multiply(a, b) { console.log('Arguments: ', a, b); return a * b; } 下面的列表包含着 JavaScript 内置的非纯函数。你可以指出它们不满足两个属性中的哪个吗？console.logelement.addEventListenerMath.randomDate.now$.ajax (这里 $ 代表你使用的 Ajax 库)理想的程序中所有的函数都是纯函数，但是从上面的函数列表可以看出，任何有意义的程序都将包含非纯函数。大多时候我们需要进行 AJAX 调用，检查当前日期或者获取一个随机数。一个好的经验法则是遵循 80/20 规则：函数中有 80％ 应该是纯函数，剩下的 20％ 的必要性将不可避免地是非纯函数。使用纯函数有几个优点：它们很容易导出和调试，因为它们不依赖于可变的状态。返回值可以被缓存或者“记忆”来避免以后重复计算。它们很容易测试，因为没有需要模拟（mock）的依赖（比如日志，AJAX，数据库等等）。你编写或者使用的函数返回空（换句话说它没有返回值），那代表它是非纯函数。不变性让我们回到捕获变量的概念上。来看看 canRide 函数。我们认为它是一个非纯函数，因为…

函数式编程简介
type: item
date: 2017-09-15T21:53:59+08:00
description: 我们来解释函数式编程的什么，它的优点是哪些，并且给出一些函数式编程的学习资源。这要看您问的是谁， 函数式编程 functional programming （FP）要么是一种理念先进的、应该广泛传播的程序设计方法；要么是一种偏学术性的、实际用途不多的编程方式。在这篇文章中我将讲解函数式编程，探究其优点，并推荐学习函数式编程的资源。语法入门本文的代码示例使用的是 Haskell 编程语言。在这篇文章中你只需要了解的基本函数语法：even :: Int -> Bool even = ... -- 具体的实现放在这里 上述示例定义了含有一个参数的函数 even ，第一行是 类型声明，具体来说就是 even 函数接受一个 Int 类型的参数，返回一个 Bool 类型的值，其实现跟在后面，由一个或多个等式组成。在这里我们将忽略具体实现方法（名称和类型已经足够了）：map :: (a -> b) -> [a] -> [b] map = ... 这个示例，map 是一个有两个参数的函数：(a -> b) ：将 a 转换成 b 的函数[a]：一个 a 的列表，并返回一个 b 的列表。（LCTT 译注： 将函数作用到 [a] （List 序列对应于其它语言的数组）的每一个元素上，将每次所得结果放到另一个 [b] ，最后返回这个结果 [b]。）同样我们不去关心要如何实现，我们只感兴趣它的定义类型。a 和 b 是任何一种的的 类型变量 type variable 。就像上一个示例中， a 是 Int 类型， b 是 Bool 类型:map even [1,2,3] 这个是一个 Bool 类型的序列：[False,True,False] 如果你看到你不理解的其他语法，不要惊慌；对语法的充分理解不是必要的。函数式编程的误区我们先来解释一下常见的误区：函数式编程不是命令行编程或者面向对象编程的竞争对手或对立面，这并不是非此即彼的。函数式编程不仅仅用在学术领域。这是真的，在函数式编程的历史中，如像 Haskell 和 OCaml 语言是最流行的研究语言。但是今天许多公司使用函数式编程来用于大型的系统、小型专业程序，以及种种不同场合。甚至还有一个面向函数式编程的商业用户[33的年度会议；以前的那些程序让我们了解了函数式编程在工业中的用途，以及谁在使用它。函数式编程与 monad 无关 ，也不是任何其他特殊的抽象。在这篇文章里面 monad 只是一个抽象的规定。有些是 monad，有些不是。函数式编程不是特别难学的。某些语言可能与您已经知道的语法或求值语义不同，但这些差异是浅显的。函数式编程中有大量的概念，但其他语言也是如此。什么是函数式编程?核心是函数式编程是只使用纯粹的数学函数编程，函数的结果仅取决于参数，而没有副作用，就像 I/O 或者状态转换这样。程序是通过 组合函数 function composition 的方法构建的：(.) :: (b -> c) -> (a -> b) -> (a -> c) (g . f) x = g (f x) 这个 中缀 infix 函数 (.) 表示的是二个函数组合成一个，将 g 作用到 f 上。我们将在下一个示例中看到它的使用。作为比较，我们看看在 Python 中同样的函数：def compose(g, f): return lambda x: g(f(x)) 函数式编程的优点在于：由于函数是确定的、没有副作用的，所以可以用结果替换函数，这种替代等价于使用使 等式推理 equational reasoning 。每个程序员都有使用自己代码和别人代码的理由，而等式推理就是解决这样问题不错的工具。来看一个示例。等你遇到这个问题：map even . map (+1) 这段代码是做什么的？可以简化吗？通过等式推理，可以通过一系列替换来分析代码：map even . map (+1) map (even . (+1)) -- 来自 'map' 的定义 map (\x -> even (x + 1)) -- lambda 抽象 map odd -- 来自 'even' 的定义 我们可以使用等式推理来理解程序并优化可读性。Haskell 编译器使用等式推理进行多种程序优化。没有纯函数，等式推理是不可能的，或者需要程序员付出更多的努力。函数式编程语言你需要一种编程语言来做函数式编程吗？在没有 高阶函数 higher-order function （传递函数作为参数和返回函数的能力）、lambdas （匿名函数）和 泛型 generics 的语言中进行有意义的函数式编程是困难的。 大多数现代语言都有这些，但在不同语言中支持函数式编程方面存在差异。 具有最佳支持的语言称为 函数式编程语言 functional programming…