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

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

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

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


当前回答

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

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

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

其他回答

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

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

与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的情况下。适用,在前面提到的页面之一。)

这是一篇关于函子的编程文章,后面更具体地介绍了它们是如何在编程语言中出现的。

函子的实际使用是在单子中,如果你想找的话,你可以找到很多关于单子的教程。

在OCaml中,它是一个参数化模块。

如果您了解c++,可以将OCaml函子视为模板。c++只有类模板,函数子在模块规模上工作。

函数子的一个例子是Map.Make;module StringMap =映射。使(字符串);;构建一个使用字符串键映射的映射模块。

你不能通过多态性实现StringMap这样的东西;你需要对这些键做一些假设。String模块包含对完全有序字符串类型的操作(比较等),函子将链接到String模块包含的操作。你可以用面向对象编程做一些类似的事情,但是你会有方法间接开销。

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

在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()中增加它,还有一些方法来检查这个变量,一切都很整齐。