概述
在函数式编程中,函子实质上是将普通的一元函数(即具有一个参数的函数)提升为新类型的变量之间的函数的构造。在普通对象之间编写和维护简单函数,并使用函子将它们抬起,然后在复杂容器对象之间手动编写函数,这要容易得多。另一个优点是只编写一次普通函数,然后通过不同的函子重新使用它们。
函子的示例包括数组,“也许”和“任一个”函子,期货(请参见例如https://github.com/Avaq/Fluture)以及许多其他函子。
插图
考虑一下从名字和姓氏构造全名的功能。我们可以将其定义fullName(firstName, lastName)
为两个参数的函数,但是不适用于仅处理一个参数的函子的函子。为了补救,我们将所有参数收集在单个对象中name
,该对象现在成为函数的单个参数:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
现在如果我们有很多人怎么办?除了手动查看列表之外,我们还可以fullName
通过map
为单行代码短的数组提供的方法简单地重用函数:
fullNameList = nameList => nameList.map(fullName)
并像这样使用
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
只要我们nameList
中的每个条目都是同时提供firstName
和lastName
属性的对象,那将起作用。但是,如果某些对象没有(甚至根本不是对象)怎么办?为了避免这些错误并使代码更安全,我们可以将对象包装成该Maybe
类型(例如https://sanctuary.js.org/#maybe-type):
// function to test name for validity
isValidName = name =>
(typeof name === 'object')
&& (typeof name.firstName === 'string')
&& (typeof name.lastName === 'string')
// wrap into the Maybe type
maybeName = name =>
isValidName(name) ? Just(name) : Nothing()
其中Just(name)
是仅包含有效名称的容器,并且Nothing()
是用于其他所有内容的特殊值。现在,我们不必中断(或忘记)检查参数的有效性,而是可以fullName
再次基于该map
方法(仅针对Maybe类型)使用另一行代码来重用(提升)原始函数。
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
并像这样使用
justSteve = maybeName(
{firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})
notSteve = maybeName(
{lastName: 'SomeJobs'}
) // => Nothing()
steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')
notSteveFN = maybeFullName(notSteve)
// => Nothing()
范畴论
甲函子在范畴理论是两类尊重其态射的组合物之间的映射。在计算机语言中,主要的关注类别是对象为类型(某些值集)且其态射为f:a->b
从一种类型a
到另一种类型的函数的对象b
。
例如,将a
其作为String
类型,b
数字类型,并将f
该函数映射为字符串的长度:
// f :: String -> Number
f = str => str.length
这里a = String
表示所有字符串b = Number
的集合和所有数字的集合。从这个意义上讲,a
和都b
代表Set类别中的对象(这与类型的类别密切相关,区别在此无关紧要)。在集合类别中,两个集合之间的态射恰好是从第一个集合到第二个集合的所有函数。因此,f
这里的长度函数是从一组字符串到一组数字的态射。
因为我们只考虑设置的类别,相关函子从它变成自己的地图发送对象,对象和态射来的态射,满足一定的代数法。
例: Array
Array
可以表示很多事情,但只有一个是Functor -类型构造,将类型映射a
到type [a]
的所有数组的类型中a
。例如,Array
函子将类型映射String
为类型[String]
(任意长度的所有字符串数组的集合),并将类型映射Number
为对应的类型[Number]
(所有数字数组的集合)。
重要的是不要混淆Functor地图
Array :: a => [a]
有态射a -> [a]
。函子只是将a
类型[a]
作为一种事物映射(关联)到另一种事物。每种类型实际上是一组元素,在这里无关紧要。相反,态射是这些集合之间的实际函数。例如,有一个自然的态射(功能)
pure :: a -> [a]
pure = x => [x]
它将一个值发送到1元素数组中,该值作为单个条目。该功能不是Array
Functor 的一部分!从该函子的角度来看,pure
它只是一个与其他函数一样的函数,没什么特别的。
另一方面,Array
函子有其第二部分-态射部分。将一个态射映射f :: a -> b
为一个态射[f] :: [a] -> [b]
:
// a -> [a]
Array.map(f) = arr => arr.map(f)
这arr
是具有type值的任意长度的任何数组a
,并且arr.map(f)
是具有type值的相同长度的数组b
,其条目是应用于f
的条目的结果arr
。为了使其成为函子,必须保持将身份映射到身份以及将成分映射到成分的数学定律,在本Array
示例中易于检查。