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

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

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

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


当前回答

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

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

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

identity :: (a) -> a

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

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

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

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

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

其他回答

在投票最多的答案下,网友Wei Hu问道:

我理解ml -函子和haskell -函子,但缺乏 将它们联系在一起的洞察力。这两者之间是什么关系 二,在分类理论的意义上?

注:本人不懂ML,如有错误请见谅。

让我们首先假设我们都熟悉“范畴”和“函子”的定义。

一个紧凑的答案是,“haskell -函子”是(endo-)函子F: Hask -> Hask,而“ML-函子”是函子G: ML- > ML'。

这里,Hask是由Haskell类型和它们之间的函数组成的类别,类似地,ML和ML'是由ML结构定义的类别。

注意:将Hask作为一个类别存在一些技术问题,但有一些方法可以绕过它们。

从范畴论的角度来看,这意味着hask -函子是Haskell类型的映射F:

data F a = ...

伴随着Haskell函数的map fmap:

instance Functor F where
    fmap f = ...

ML是差不多的,尽管我不知道有一个规范的fmap抽象,所以让我们定义一个:

signature FUNCTOR = sig
  type 'a f
  val fmap: 'a -> 'b -> 'a f -> 'b f
end

f映射ml -类型fmap映射ml -函数

functor StructB (StructA : SigA) :> FUNCTOR =
struct
  fmap g = ...
  ...
end

是一个函子F: StructA -> StructB。

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

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

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

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

module SSet = MakeSet(StringCaseEqual);;

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

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

函子是对象和态射的映射,它保留了一个类别的组成和身份。

让我们定义什么是类别?

是一堆东西! 在a内部画几个点(现在是两个点,一个是“a”,另一个是“b”) 圆圈,并命名为圆圈A(类别)。

这个类别包含什么?

对象之间的组合和每个对象的恒等函数。

因此,在应用Functor之后,我们必须映射对象并保存组合。

让我们想象‘A’是我们的范畴,它有对象[' A', 'b'],并且存在一个态射A -> b

现在,我们必须定义一个函子,它可以将这些对象和态射映射到另一个类别“B”。

假设这个函子叫做Maybe

data Maybe a = Nothing | Just a

B类是这样的。

请再画一个圆,但这次用“也许a”和“也许b”代替“a”和“b”。

一切看起来都很好,所有的对象都被映射了

“a”变成了“也许a”,“b”变成了“也许b”。

但问题是我们也要把态射从a映射到b。

这意味着' a'中的态态a -> b应该映射到'可能a' -> '可能b'

来自a -> b的形态称为f,然后来自'Maybe a' -> 'Maybe b'的形态称为'fmap f'

现在让我们看看函数f在A中做了什么,看看我们能否在B中复制它

A中f的函数定义:

f :: a -> b

F取a并返回b

f在B中的函数定义:

f :: Maybe a -> Maybe b

f取也许a,返回也许b

让我们看看如何使用fmap将函数'f'从'A'映射到'B'中的函数'fmap f'

fmap的定义

fmap :: (a -> b) -> (Maybe a -> Maybe b)
fmap f Nothing = Nothing
fmap f (Just x) = Just(f x)

那么,我们在这里做什么?

我们将函数“f”应用于类型为“a”的“x”。“Nothing”的特殊模式匹配来自于Functor Maybe的定义。

因此,我们将对象[a, b]和形态[f]从类别' a '映射到类别' b '。

那是富克托!

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

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

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

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