在阅读各种关于函数式编程的文章时,我遇到过几次“Functor”这个术语,但作者通常认为读者已经理解了这个术语。在网络上你可以看到一些过于技术性的描述(参见维基百科的文章),也可以看到一些非常模糊的描述(参见ocaml-tutorial网站上关于函数函数的部分)。

有没有人可以定义这个术语,解释它的用法,或者提供一个如何创建和使用函子的例子?

编辑:虽然我对这个术语背后的理论很感兴趣,但我对这个概念的实现和实际应用更感兴趣,而不是理论。

编辑2:看起来好像有一些交叉术语:我特别指的是函数式编程的函子,而不是c++的函数对象。


当前回答

有三种不同的意思,没有太大的联系!

In Ocaml it is a parametrized module. See manual. I think the best way to grok them is by example: (written quickly, might be buggy) module type Order = sig type t val compare: t -> t -> bool end;; module Integers = struct type t = int let compare x y = x > y end;; module ReverseOrder = functor (X: Order) -> struct type t = X.t let compare x y = X.compare y x end;; (* We can order reversely *) module K = ReverseOrder (Integers);; Integers.compare 3 4;; (* this is false *) K.compare 3 4;; (* this is true *) module LexicographicOrder = functor (X: Order) -> functor (Y: Order) -> struct type t = X.t * Y.t let compare (a,b) (c,d) = if X.compare a c then true else if X.compare c a then false else Y.compare b d end;; (* compare lexicographically *) module X = LexicographicOrder (Integers) (Integers);; X.compare (2,3) (4,5);; module LinearSearch = functor (X: Order) -> struct type t = X.t array let find x k = 0 (* some boring code *) end;; module BinarySearch = functor (X: Order) -> struct type t = X.t array let find x k = 0 (* some boring code *) end;; (* linear search over arrays of integers *) module LS = LinearSearch (Integers);; LS.find [|1;2;3] 2;; (* binary search over arrays of pairs of integers, sorted lexicographically *) module BS = BinarySearch (LexicographicOrder (Integers) (Integers));; BS.find [|(2,3);(4,5)|] (2,3);;

您现在可以快速添加许多可能的顺序,形成新顺序的方法,轻松地对它们进行二进制或线性搜索。泛型编程。

In functional programming languages like Haskell, it means some type constructors (parametrized types like lists, sets) that can be "mapped". To be precise, a functor f is equipped with (a -> b) -> (f a -> f b). This has origins in category theory. The Wikipedia article you linked to is this usage. class Functor f where fmap :: (a -> b) -> (f a -> f b) instance Functor [] where -- lists are a functor fmap = map instance Functor Maybe where -- Maybe is option in Haskell fmap f (Just x) = Just (f x) fmap f Nothing = Nothing fmap (+1) [2,3,4] -- this is [3,4,5] fmap (+1) (Just 5) -- this is Just 6 fmap (+1) Nothing -- this is Nothing

因此,这是一种特殊的类型构造函数,与Ocaml中的函子关系不大!

在命令式语言中,它是指向函数的指针。

其他回答

粗略的概述

在函数式编程中,函子本质上是将普通一元函数(即只有一个参数的函数)提升为新类型变量之间的函数的构造。在普通对象之间编写和维护简单函数并使用函子来提升它们要容易得多,而在复杂的容器对象之间手动编写函数要容易得多。进一步的好处是只编写一次普通函数,然后通过不同的函子重用它们。

函子的例子包括数组,“maybe”和“either”函子,期货(参见例如https://github.com/Avaq/Fluture),以及许多其他函子。

插图

考虑从姓和名构造完整人名的函数。我们可以像fullName(firstName, lastName)那样将其定义为两个参数的函数,但这不适用于只处理一个参数的函数的函子。为了补救,我们将所有参数收集到一个对象名称中,现在它成为了函数的单个参数:

// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName

如果数组中有很多人呢?不需要手动遍历列表,我们可以通过为数组提供的map方法重用函数fullName,该方法只有短短的一行代码:

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()

范畴论

范畴论中的函子是两个范畴之间关于它们的态射组成的映射。在计算机语言中,主要感兴趣的范畴是其对象是类型(某些值集),其形态是从一种类型a到另一种类型b的函数f:a->b。

例如,设a为String类型,b为Number类型,f为将字符串映射到其长度的函数:

// f :: String -> Number
f = str => str.length

这里a = String表示所有字符串的集合,b = Number表示所有数字的集合。在这种意义上,a和b都表示集合类别中的对象(这与类型类别密切相关,在这里没有区别)。在集合范畴中,两个集合之间的态射正是从第一个集合到第二个集合的所有函数。所以这里的长度函数f是从字符串集合到数字集合的态射。

由于我们只考虑集合范畴,从它到它自身的相关函子是满足某些代数定律的映射,将对象发送到对象,将态射发送到态射。

例如:数组

数组可以有很多含义,但只有一种是Functor——类型构造,将类型a映射到类型a的所有数组的类型[a]。例如,Array Functor将类型String映射到类型[String](所有任意长度的字符串数组的集合),并将类型Number映射到相应的类型[Number](所有数字数组的集合)。

重要的是不要混淆Functor映射

Array :: a => [a]

a -> [a]。函数子只是将类型a映射(关联)到类型[a],作为一种东西到另一种东西。每种类型实际上是一组元素,这在这里无关紧要。相反,态射是这些集合之间的实际函数。例如,有一个自然形态(函数)

pure :: a -> [a]
pure = x => [x]

它将一个值发送到一个元素数组,并将该值作为单个项。该函数不是数组函子的一部分!从这个函子的角度来看,纯函数和其他函数一样,没什么特别的。

另一方面,Array Functor有它的第二部分——形态部分。将一个态射f:: a -> b映射为一个态射[f]:: [a] -> [b]:

// a -> [a]
Array.map(f) = arr => arr.map(f)

这里arr是任意长度的数组,值类型为a, arr.map(f)是相同长度的数组,值类型为b,它的项是对arr的项应用f的结果。要使它成为函子,必须遵守单位到单位、组合到组合的映射数学定律,在这个Array示例中很容易检查。

简单地说,函子或函数对象是可以像函数一样调用的类对象。

在c++中:

这就是写函数的方法

void foo()
{
    cout << "Hello, world! I'm a function!";
}

这就是写函子的方法

class FunctorClass
{
    public:
    void operator ()
    {
        cout << "Hello, world! I'm a functor!";
    }
};

现在你可以这样做:

foo(); //result: Hello, World! I'm a function!

FunctorClass bar;
bar(); //result: Hello, World! I'm a functor!

这样做的好处是可以将state保存在类中——想象一下,如果您想询问一个函数被调用了多少次。没有办法以简洁、封装的方式做到这一点。对于函数对象,它就像任何其他类一样:你有一些实例变量,你在operator()中增加它,还有一些方法来检查这个变量,一切都很整齐。

在Inria网站上的O'Reilly OCaml书中有一个很好的例子(不幸的是,在写这篇文章时,它被删除了)。我在加州理工学院使用的这本书中找到了一个非常相似的例子:OCaml介绍(pdf链接)。相关的部分是关于函子的章节(书中139页,PDF中149页)。

在书中,他们有一个名为MakeSet的函子,它创建了一个由列表组成的数据结构,以及添加元素、确定元素是否在列表中以及查找元素的函数。用于确定它是否在集合中的比较函数已被参数化(这是使MakeSet成为函子而不是模块的原因)。

它们还有一个实现比较函数的模块,这样就可以进行不区分大小写的字符串比较。

使用函子函数和实现比较的模块,它们可以在一行中创建一个新模块:

module SSet = MakeSet(StringCaseEqual);;

这将为使用不区分大小写比较的一组数据结构创建一个模块。如果您想创建一个使用区分大小写比较的集合,那么您只需要实现一个新的比较模块,而不是一个新的数据结构模块。

Tobu将函子与c++中的模板进行了比较,我认为这是非常恰当的。

有三种不同的意思,没有太大的联系!

In Ocaml it is a parametrized module. See manual. I think the best way to grok them is by example: (written quickly, might be buggy) module type Order = sig type t val compare: t -> t -> bool end;; module Integers = struct type t = int let compare x y = x > y end;; module ReverseOrder = functor (X: Order) -> struct type t = X.t let compare x y = X.compare y x end;; (* We can order reversely *) module K = ReverseOrder (Integers);; Integers.compare 3 4;; (* this is false *) K.compare 3 4;; (* this is true *) module LexicographicOrder = functor (X: Order) -> functor (Y: Order) -> struct type t = X.t * Y.t let compare (a,b) (c,d) = if X.compare a c then true else if X.compare c a then false else Y.compare b d end;; (* compare lexicographically *) module X = LexicographicOrder (Integers) (Integers);; X.compare (2,3) (4,5);; module LinearSearch = functor (X: Order) -> struct type t = X.t array let find x k = 0 (* some boring code *) end;; module BinarySearch = functor (X: Order) -> struct type t = X.t array let find x k = 0 (* some boring code *) end;; (* linear search over arrays of integers *) module LS = LinearSearch (Integers);; LS.find [|1;2;3] 2;; (* binary search over arrays of pairs of integers, sorted lexicographically *) module BS = BinarySearch (LexicographicOrder (Integers) (Integers));; BS.find [|(2,3);(4,5)|] (2,3);;

您现在可以快速添加许多可能的顺序,形成新顺序的方法,轻松地对它们进行二进制或线性搜索。泛型编程。

In functional programming languages like Haskell, it means some type constructors (parametrized types like lists, sets) that can be "mapped". To be precise, a functor f is equipped with (a -> b) -> (f a -> f b). This has origins in category theory. The Wikipedia article you linked to is this usage. class Functor f where fmap :: (a -> b) -> (f a -> f b) instance Functor [] where -- lists are a functor fmap = map instance Functor Maybe where -- Maybe is option in Haskell fmap f (Just x) = Just (f x) fmap f Nothing = Nothing fmap (+1) [2,3,4] -- this is [3,4,5] fmap (+1) (Just 5) -- this is Just 6 fmap (+1) Nothing -- this is Nothing

因此,这是一种特殊的类型构造函数,与Ocaml中的函子关系不大!

在命令式语言中,它是指向函数的指针。

这个问题的最佳答案在布伦特·约吉(Brent Yorgey)的《type eclassopedia》中找到。

这一期的单子阅读器包含了一个精确的定义,什么是一个函子以及许多其他概念的定义以及一个图表。(Monoid, Applicative, Monad和其他概念被解释和看到与函子的关系)。

http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

摘自Functor的Typeclassopedia: 一个简单的直觉是,一个Functor代表一个容器 类中的每个元素统一应用函数的能力 容器”

但实际上,所有类型的类目都是强烈推荐的,因为它们出奇地简单。在某种程度上,你可以看到这里的类型类与object中的设计模式是平行的,因为它们为给定的行为或能力提供了词汇表。

干杯