JavaScript 中的函子
JavaScript 中的函子
原文:https://medium.com/hackernoon/functors-in-javascript-20a647b8f39f

where F is a Functor
“数学家就像画家或诗人一样,是图案的制造者。如果他的模式比他们的更持久,那是因为他们是用思想创造的。”
——g·h·哈代,一位数学家的道歉
有时,在实际编程中,由于数据固有的复杂性,逻辑准确性是不可能的。数据抽象是一个非常有用的工具,它帮助我们创建数据的简化表示。
为了实现这一点,我们创建了“容器,这些容器只包含我们的数据,不做任何其他事情,我们不像在 OOP 中那样为它们提供属性或方法。
我们取一个值,并把它放在我们的容器中,容器将保护我们的值的安全,同时我们通过我们漂亮的函数逻辑传递它。只有当我们迫切需要时,我们才会取出那个值。这样,我们的容器将只有两个任务:
- 保持自身内部的价值。
- 只有在我们需要的时候才把价值还给我们。
此外,永远不要改变其中的值。
这些容器并不是什么新东西,自从我们开始使用 Java 脚本以来,我们就一直在使用它们,只有现在了解它们才能让我们成为更强大的程序员。
当我们进行函数式编程时,这些容器非常强大,因为它们有助于我们函数构造的基础,并帮助我们使用常见的技术,如纯函数式错误处理和异步操作(以及许多其他东西)。

在我们深入讨论这些容器之前,我们将讨论一种特殊类型的容器,称为 函子。
那么,什么是函子呢? 函子是可以与‘映射’函数一起使用的容器。
在我们创建自己的容器之前,让我们先来看看一些从小到大一直在使用的常见容器。
数组

数组是我们在日常编程中最常用的容器,它们不仅包含一个值,还包含多个值。它们是所有数据抽象中最简单的,但却如此强大。让我们和他们一起工作,
const arr = [ 8, 10, 23, 35, 54 ];
我们可以从这些数据中得到数值,比如
const b = a[1];
现在,如果你保证只在数组上使用这些操作,我保证这些数组将永远是你的朋友。
永远不要修改原始数组,
arr.push(45) ❌orarr[1] = 45 ❌
但是你可以用它们来创造新的,
const arr2 = [ ...arr, 38, 52 ]orconst even = filter(x => x%2 === 0, arr)
我们从来没有在数组上应用任何可能改变数组中值的方法,我们只是创建数组或者取出值,仅此而已。
虽然 Java 的人会说这样做使我的数组变得毫无用处,但我会说,“不,我刚刚创建了一个强大得超乎你想象的数组”。
是的,数组是一个函子。
函子是一个容器,它可以被一元函数映射。
当我说 mapped upon 时,我的意思是这个容器可以用一个特殊的函数(比如说 fmap 或 map )来处理,这个函数将任何一元函数应用于这个容器的每个内容,并返回一个包含结果值的容器作为它的内容。

This truck is a functor
在数组的情况下,这个特殊函数被简单地称为映射函数。
地图功能

Not this Map
Map function in arrays 接受一个数组,并对其所有元素逐一应用特定的函数,然后返回另一个数组。
[1,2,3,4].map(multiplyBy2)
*//=> [2,4,6,8]*or**map(multiplyBy2, [1,2,3,4])
*//=> [2,4,6,8]****where multiplyBy2 = x => x * 2 and map = (fn, arr) => arr.map(fn)*
因为,我们总是从映射中得到另一个数组,我们总是可以再次映射它来创建一个数组转换链。
[1,2,3].**map**(x => x * 3).**map**(x => x * 2).**map**(x => x / 6)
Map 函数不仅仅是一个迭代器函数,记住,当我们的值在一个容器中时,我们不能直接对它应用一个函数,然后期望值被改变。举个例子,
const a = [1, 2, 3]String(a) = ‘[1 ,2, 3]’ and not [‘1’, ‘2’, ‘3’]
一个 map 函数给一个函数访问容器的内容。
map(String, [1, 2, 3]) = [‘1’, ‘2’, ‘3’]
此外,映射函数从不改变容器,相反,它只是对其内容进行操作。保持容器不动。

地图不会改变容器的类型,但是它可以改变里面的东西的类型。
内容的类型可能会改变,我们可以从 map 函数的类型定义中看出这一点。
map :: (a -> b) -> [a] -> [b]orfmap :: (a -> b) -> F a -> F b
这里,a和b可以是相同的类型,也可以是不同的类型。
现在,如果你仔细看,你会看到地图函数从a -> b获取一个函数,并从Fa -> Fb返回一个函数
这里,a -> b指的是任何一个接受a并返回b的一元函数,比如
multiplyBy2(3) = 6 // is a -> b as 3 -> 6
而Fa -> Fb表示任何一元函数,它接受一个内部带有a的容器,并返回一个内部带有b的容器。
multiplyArrBy2([1]) = [2] // is Fa -> Fb as [1] -> [2], F is []
要了解我们如何使用这些类型,请阅读此处,
当一个 Javascript 开发人员开始探索函数式编程的深层秘密时,他经常会发现这些…
hackernoon.com](https://hackernoon.com/function-type-signatures-in-javascript-5c698c1e9801)
现在我们的地图功能将从multiplyBy2变为multiplyArrBy2让我们看看这个,
现在我们的函数multiplyBy2过去作用于整数,现在因为map将作用于整数数组。在某种程度上,我们的 map 函数提升了或者说 提升了 我们的函数,这样它就可以在这种情况下作用于我们的容器或数组。
我们偶尔会提升我们的函数,这样它们就可以作用于我们容器中的数据。(因为正常功能不能)
我们尽可能使用部分应用的地图功能,因为这样我们就可以用它来创建另一个功能,或者我们可以将它与合成功能一起使用,当数据非常紧急时,我们将使用我们正常的地图功能。
为什么要使用部分应用的函数?
为函数提供比预期更少的参数称为函数的部分应用。
hackernoon.com](https://hackernoon.com/partial-application-of-functions-dbe7d9b80760)
在更进一步之前, 我们知道,我们用容器来保存我们的价值观,它们唯一的目的就是保存它们。我们还知道 函子 是特殊的容器,我们可以在上面进行 映射 操作。我们知道如何在数组上使用映射函数,我们也见过我们的第一个容器和一个函子,数组,我们在日常编程中经常使用。
现在,让我们来看看第二个容器,它也是一个函子。这个容器从我们的功能冒险开始就一直伴随着我们。
功能
所有的函数也是函子,因此也是容器。
容器的功能是什么?
好吧,容器包含数据,但是我们的函数明明包含逻辑,函数怎么可能是容器。
如果你仔细想想:一个函数在被调用时,会返回一个值。所以在某种程度上,它包含了我们的值,唯一的区别是这个值是动态计算的。
aFunction(45) // => 90So aFunction gives the value 90, when it is passed 45
把它们想象成无限数量的值的数组,如果你想从这些数组中得到一个特定的值,你需要用一个或多个特定的参数来调用函数。

Functions are arrays with Infinite Values
那么,函数是像数组一样的容器?
是的,就像数组在传递索引时给出值一样,函数在传递参数时给出结果。
const a = [ 8, 10, 23, 35, 54 ]const f = z => z * 2 a[1] = 10
f(2) = 4
只有我们的数组比较弱,因为它们只给出范围整数的结果作为索引,但是我们的函数可以接受任何类型的参数,因为对它们没有限制,它们也可以接受其他函数作为参数。
函数是有无限个值的容器。
因为它们是函子,所以它们也有一个映射?
是的,它是这样定义的
const fnMap = (f, mappingFn) => (x => f(mappingFn(x)))
就像 map 函数获取数组,将函数应用于内容并返回数组。类似地, fnMap 接受一个函数,将另一个函数应用于它的结果并返回该函数,在某种程度上它组合了两个函数 s uch,一个函数的结果是第二个函数的参数。
嗯,好吧,让我们用这个
const multiplyBy6 = fnMap(multiplyBy2, multiplyBy3)
这看起来很熟悉,我们以前见过。
是我们的作曲功能。

因此,我们的函数也有一个映射函数,如果我们将一个函数映射到另一个函数,我们就在某种程度上融合了这两个函数。映射函数称为合成函数。
TL;博士
- 假设我们有一个函数
multiplyBy2。 - 在从中取出值或者调用它之前,我们用
multiplyBy3映射它。 - 所以
multiplyBy2里面的所有值都乘以 3。 - 现在,每当我们调用
multiplyBy2比如说x,我们得到的值将是x * 3 * 2。
也许,现在这一点会更清楚,
const fnMap = (f, mappingFn) => (x => f(mappingFn(x)))
函数就像数组,是一种数据抽象。只有函数根据我们的要求计算数据。
或者我们可以说数组就像函数,当我们对它们使用'[]'时,它们会立即给出结果。
我们可以用任何方式弯曲我们的思想来理解这一点,这都是真的。
我们只需要记住一件事,我们的值只有在容器中才有价值,无论是函数,数组还是我们创建的容器,只有这样,我们才能将代码中复杂的结构,可能是由于数据的复杂性而产生的,分解成简单的结构。
从现在开始,我需要你记住这个东西,在函数式编程 中永远不要赤裸裸地使用你的数据 。总是把它包在容器里。
现在,这篇文章太长了,无法讨论更多关于函子的内容。但我保证,我们会在另一部里继续。

用...书写💖。