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

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

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

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


当前回答

考虑到其他的答案和我现在要发布的内容,我想说这是一个相当沉重的重载词,但无论如何……

关于Haskell中'functor'这个词的含义,可以问GHCi:

Prelude> :info Functor
class Functor f where
  fmap :: forall a b. (a -> b) -> f a -> f b
  (GHC.Base.<$) :: forall a b. a -> f b -> f a
        -- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base

基本上,Haskell中的函子是可以被映射的。另一种说法是,函子是可以被视为容器的东西,它可以被要求使用给定的函数来转换它所包含的值;因此,对于列表,fmap与map重合,对于Maybe, fmap f (Just x) = Just (f x), fmap f Nothing = Nothing等。

函子类型类小节和《Learn You a Haskell for Great Good》的函子、应用函子和Monoids小节给出了一些例子,说明这个特定概念在哪里有用。(总结一下:很多地方!: -))

请注意,任何单子都可以被视为函子,事实上,正如Craig Stuntz所指出的,最常用的函子往往是单子……对了,有时使一个类型成为Functor类型类的实例是很方便的,而不需要麻烦地使它成为一个单子。(例如,在Control中的ZipList的情况下。适用,在前面提到的页面之一。)

其他回答

这里的其他答案是完整的,但我将尝试另一种解释FP使用函子。我们来做个类比:

函子是类型为A的容器,当它受到从A→b映射的函数的作用时,将产生类型为b的容器。

与c++中使用的抽象函数指针不同,这里函子不是函数;相反,它是一种在受到某个函数影响时表现一致的东西。

在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中的函子关系不大!

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

不是为了与前面的理论或数学答案相矛盾,但Functor也是一个对象(在面向对象编程语言中),它只有一个方法,并且可以有效地用作函数。

Java中的Runnable接口就是一个例子,它只有一个方法:run。

考虑这个例子,首先在Javascript中,它有一级函数:

[1, 2, 5, 10].map(function(x) { return x*x; });

输出: [1,4,25,100]

map方法接受一个函数并返回一个新数组,其中每个元素都是将该函数应用于原始数组中相同位置的值的结果。

要在Java中使用Functor做同样的事情,你首先需要定义一个接口,比如:

public interface IntMapFunction {
  public int f(int x);
}

然后,如果你添加一个集合类,它有一个映射函数,你可以这样做:

myCollection.map(new IntMapFunction() { public int f(int x) { return x * x; } });

这使用了IntMapFunction的一个内嵌子类来创建一个Functor,它是前面JavaScript示例中的函数的OO等效。

使用函子可以在OO语言中应用函数式技术。当然,一些OO语言也直接支持函数,所以这不是必需的。

参考:http://en.wikipedia.org/wiki/Function_object

在函数式编程中,错误处理是不同的。抛出和捕获异常是命令式代码。不是使用try/catch块,而是围绕可能抛出错误的代码创建一个安全框。这是函数式编程中的基本设计模式。包装器对象用于封装可能错误的值。包装器的主要目的是提供一种使用被包装对象的“不同”方式

 const wrap = (val) => new Wrapper(val);

包装可以保护对值的直接访问,以便对它们进行操作 安全而不可改变。因为我们不能直接得到它,所以提取它的唯一方法就是使用恒等函数。

identity :: (a) -> a

这是恒等函数的另一个用例:从封装的类型中功能地提取数据。

Wrapper类型使用映射来安全地访问和操作值。在本例中,我们将恒等函数映射到容器上,以从容器中提取值。使用这种方法,可以在调用函数之前检查是否为null,或者检查是否为空字符串、负数等等。

 fmap :: (A -> B) -> Wrapper[A] -> Wrapper[B]

Fmap,首先打开容器,然后将给定函数应用于它的值,最后将值关闭到相同类型的新容器中。这种类型的函数称为函子。

Fmap在每次调用时返回容器的新副本。 函子没有副作用 函子必须是可组合的