在阅读各种关于函数式编程的文章时,我遇到过几次“Functor”这个术语,但作者通常认为读者已经理解了这个术语。在网络上你可以看到一些过于技术性的描述(参见维基百科的文章),也可以看到一些非常模糊的描述(参见ocaml-tutorial网站上关于函数函数的部分)。
有没有人可以定义这个术语,解释它的用法,或者提供一个如何创建和使用函子的例子?
编辑:虽然我对这个术语背后的理论很感兴趣,但我对这个概念的实现和实际应用更感兴趣,而不是理论。
编辑2:看起来好像有一些交叉术语:我特别指的是函数式编程的函子,而不是c++的函数对象。
在Inria网站上的O'Reilly OCaml书中有一个很好的例子(不幸的是,在写这篇文章时,它被删除了)。我在加州理工学院使用的这本书中找到了一个非常相似的例子:OCaml介绍(pdf链接)。相关的部分是关于函子的章节(书中139页,PDF中149页)。
在书中,他们有一个名为MakeSet的函子,它创建了一个由列表组成的数据结构,以及添加元素、确定元素是否在列表中以及查找元素的函数。用于确定它是否在集合中的比较函数已被参数化(这是使MakeSet成为函子而不是模块的原因)。
它们还有一个实现比较函数的模块,这样就可以进行不区分大小写的字符串比较。
使用函子函数和实现比较的模块,它们可以在一行中创建一个新模块:
module SSet = MakeSet(StringCaseEqual);;
这将为使用不区分大小写比较的一组数据结构创建一个模块。如果您想创建一个使用区分大小写比较的集合,那么您只需要实现一个新的比较模块,而不是一个新的数据结构模块。
Tobu将函子与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的情况下。适用,在前面提到的页面之一。)
简单地说,函子或函数对象是可以像函数一样调用的类对象。
在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()中增加它,还有一些方法来检查这个变量,一切都很整齐。
你回答了不少不错的问题。我将加入:
函子,在数学意义上,是代数上一种特殊的函数。它是将一个代数映射到另一个代数的最小函数。“极简性”用函子定律来表示。
有两种方式来看待这个问题。例如,列表是某些类型的函子。也就是说,给定类型为“a”的代数,您可以生成包含类型为“a”的列表的兼容代数。(例如:将一个元素带到包含它的单元素列表的映射:f(a) = [a])同样,兼容性的概念是由函子定律表示的。
另一方面,鉴于函子f / a型,(也就是说,f是应用函子的结果f的代数a型),从g和功能:- > b,我们可以计算一个新的函子f = (fmap g)映射f a到f b。简而言之,fmap是f的一部分映射“函子零件”“函子零件”,和g函数的一部分,“代数”映射到“代数部分”。它接受一个函数,一个函子,一旦完成,它也是一个函子。
看起来不同的语言使用不同的函子概念,但事实并非如此。它们只是在不同的代数上使用函子。OCamls有一个模块代数,这个代数上的函子允许您以一种“兼容”的方式将新声明附加到模块。
Haskell函子不是类型类。它是一个具有满足类型类的自由变量的数据类型。如果您愿意深入挖掘数据类型的精髓(没有自由变量),您可以通过底层代数将数据类型重新解释为函子。例如:
数据F = F Int
是整型类的同构。F,作为一个值构造函数,是一个将Int映射到F Int的函数,一个等价的代数。它是一个函子。另一方面,这里的fmap不是免费的。这就是模式匹配的作用。
函子很适合以一种代数相容的方式将事物“附加”到代数元素上。