在函数式编程中,什么是函子?
在阅读关于函数式编程的各种文章时,我几次遇到'函子'这个词,但作者通常假设读者已经理解这个术语。 环顾网络提供了过度的技术性描述(参见维基百科文章)或难以置信的模糊描述(请参阅此ocaml-tutorial网站上有关Functors的部分)。
有人可以善意地定义这个术语,解释它的用法,也许可以提供一个关于如何创建和使用仿函数的例子。
编辑 :虽然我对这个术语背后的理论很感兴趣,但我对这一理论的兴趣不如我在实施和实际使用这个概念。
编辑2 :看起来像是有一些跨代术语正在发生:我特指函数式编程的函子,而不是C ++的函数对象。
“函子”一词来自类别理论,这是一个非常普遍,非常抽象的数学分支。 功能语言的设计者至少以两种不同的方式借用它。
在ML语言系列中,仿函数是一个将一个或多个其他模块作为参数的模块。 它被认为是一项高级功能,大多数初学程序员都遇到了困难。
作为实现和实际应用的一个例子,你可以一劳永逸地定义你最喜欢的平衡二叉搜索树形式,它将作为参数提供一个模块,它提供:
在二叉树中使用的键的类型
按键上的总排序功能
一旦你完成了这个任务,你可以永远使用相同的平衡二叉树实现。 (树中存储的值的类型通常是多态的 - 树不需要查看除复制它们之外的值,而树肯定需要能够比较键,并从中获取比较函数函子的参数。)
ML仿函数的另一个应用是分层网络协议。 链接是由CMU福克斯集团撰写的一篇非常了不起的论文; 它展示了如何使用仿函数在更简单的图层类型(如IP或甚至直接通过以太网)上构建更复杂的协议层(如TCP)。 每个图层都是作为一个函子实现的,它将下面的图层作为参数。 软件的结构实际上反映了人们思考问题的方式,而不是程序员头脑中存在的层。 1994年这项工作发表时,这是一件大事。
对于一个ML函子的实例,你可以看到ML Module Mania,它包含一个可发布函数的例子(即可怕的例子)。 有关ML模块系统(与其他类型模块进行比较)的精彩,清晰,透彻的解释,请阅读Xavier Leroy辉煌的1994 POPL纸张清单类型,模块和独立汇编的前几页。
在Haskell和一些相关的纯函数式语言中, Functor
是一个类型类。 一个类型属于一个类型类(或者更技术上讲,类型“是类型类的一个实例),当类型提供某些预期行为的某些操作时。 如果类型T
具有某种类似集合的行为,则它可以属于类Functor
:
类型T
是通过另一种类型进行参数化的,您应该将其视为集合的元素类型。 如果您包含整数,字符串或布尔值,那么完整集合的类型就类似于T Int
, T String
, T Bool
。 如果元素类型未知,则将其写为类型参数a
,如T a
。
实例包括列表(类型的零个或多个元件a
),则Maybe
类型(Type的零个或一个元件a
),套类型的元素的a
,类型的元件的阵列a
,各种包含类型的值搜索树的a
,还有很多其他你可以想到的。
T
必须满足的另一个属性是,如果你有一个类型a -> b
(对元素的函数)的函数,那么你必须能够使用该函数并在集合上产生一个相关函数。 您可以使用运算符fmap
执行此操作,该运算符由Functor
类型类中的每个类型共享。 运算符实际上是重载的,所以如果你有一个函数, even
类型为Int -> Bool
,那么
fmap even
是一个重载函数,可以做许多美妙的事情:
将整数列表转换为布尔值列表
将整数树转换为布尔值树
将Nothing
转换为Nothing
, Just 7
转换为Just False
在Haskell中,通过给出fmap
的类型来表示该属性:
fmap :: (Functor t) => (a -> b) -> t a -> t b
我们现在有一个小的t
,这意味着“ Functor
类中的任何类型”。
长话短说,在Haskell中, 一个函数是一种集合,如果给它一个函数元素, fmap
会给你一个集合函数 。 正如你可以想象的那样,这是一个可以广泛重用的思想,这就是为什么它作为Haskell标准库的一部分而受到祝福的原因。
像往常一样,人们不断发明新的,有用的抽象概念,并且您可能需要研究应用函子,最好的参考文献可能是Conor McBride和Ross Paterson撰写的名为Applicative Programming with Effects的论文。
这里的其他答案是完整的,但我会尝试使用函子的FP的另一种解释。 以此作为比喻:
函子是一个类型为a的容器,当它受到从a→b映射的函数时,产生一个类型为b的容器。
与C ++中抽象函数指针的使用不同,这里函数不是函数; 相反,当它受到函数的影响时,它的行为是一致的。
有三种不同的含义,没有多大关系!
在Ocaml中,它是一个参数化模块。 参见手册。 我认为最好的方法是举例:(写得很快,可能是越野车)
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);;
您现在可以快速添加许多可能的订单,形成新订单的方式,轻松地在它们上执行二进制或线性搜索。 通用编程FTW。
在像Haskell这样的函数式编程语言中,它意味着可以被“映射”的一些类型构造函数(参数化类型,如列表,集合)。 准确地说,一个仿函数f
配备了(a -> b) -> (fa -> fb)
。 这源于类别理论。 您链接到的维基百科文章就是这种用法。
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中的函数无关!