在函数式编程中,函数是什么?

在阅读关于函数式编程的各种文章时,我碰到过几次“函子”,但作者通常假设读者已经理解了这个术语。 环顾networking提供了过度的技术性描述(参见维基百科文章 )或难以置信的模糊描述(请参阅此ocaml教程网站上有关Functors的部分)。

有人可以善意地定义这个术语,解释它的用法,或者提供一个关于如何创build和使用函子的例子。

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

编辑2 :看起来有一些交叉terminoligy进行:我特指函数式编程functors,而不是C ++的函数对象。

“函数”一词来自类别理论,这是一个非常普遍的,非常抽象的math分支。 它至less以两种不同的方式被function语言的devise者借用。

  • 在ML系列语言中,仿函数是一个将一个或多个其他模块作为参数的模块。 这被认为是一个先进的function,大多数开始程序员都有困难。

    作为实现和实际应用的一个例子,您可以一劳永逸地定义您最喜欢的平衡二叉search树forms,并将其作为模块参数提供:

    • 在二叉树中使用的键的types

    • 按键上的总sortingfunction

    一旦你完成了这个,你可以永远使用相同的平衡二叉树实现。 (存储在树中的值的types通常是多态的 – 树不需要查看除复制它们之外的值,而树肯定需要能够比较键,并从中获取比较函数函子的参数。)

    ML仿函数的另一个应用是分层networking协议 。 链接是由CMU福克斯集团一个非常了不起的论文; 它展示了如何使用函数来构build更简单的图层types(如IP或甚至直接通过以太网)更复杂的协议层(如TCP)。 每一层都作为一个函数来实现,它作为参数在下面的图层。 软件的结构实际上反映了人们思考问题的方式,而不是程序员头脑中的层级。 1994年这项工作发表的时候,这是一个大问题。

    对于ML函子的一个很好的例子,你可以看到ML Module Mania ,它包含了一个函数的可发布的例子(即可怕的例子)。 对于ML模块系统(与其他types的模块进行比较)的精彩,清晰,透彻的解释,请阅读Xavier Leroy辉煌的1994 POPL纸张清单types,模块和独立汇编的前几页。

  • 在Haskell和一些相关的纯函数语言中, Functor是一个types类 。 一个types属于一个types类(或者更技术上说,types“是types类的一个实例),当types提供了某些预期行为的操作时。 如果typesT具有某些类似collections的行为,则它可以属于类Functor

    • typesT是通过另一种types进行参数化的,您应该将其视为集合的元素types。 如果您包含整数,string或布尔值,那么完整集合的types就类似于T IntT StringT Bool 。 如果元素types是未知的,则将其写为types参数 a ,如在T a

      示例包括列表(零个或多个types为a元素), Maybetypes(零个或一个types为a的元素),types为a的元素集合,types为a的元素的数组,包含typesa ,还有很多其他你可以想到的。

    • T必须满足的另一个属性是,如果你有一个typesa -> b (元素上的函数)的函数,那么你必须能够使用这个函数并且在集合上产生一个相关的函数。 您可以使用运算符fmap完成此操作,该运算符由Functortypes类中的每个types共享。 运算符实际上是重载的,所以如果你有一个函数, eventypes为Int -> Bool ,那么

       fmap even 

      是一个重载函数,可以做许多美好的事情:

      • 将整数列表转换为布尔值列表

      • 将整数树转换为布尔值树

      • Nothing转换为NothingJust 7将其转换为Just False

      在Haskell中,通过给出fmap的types来表示该属性:

       fmap :: (Functor t) => (a -> b) -> ta -> tb 

      我们现在有一个小的t ,这意味着“ Functor类中的任何types”。

    长话短说,在Haskell中, 一个函子是一种集合,如果给它一个函数元素, fmap会给你一个函数集合 。 正如你所想象的,这是一个可以被广泛重用的思想,这就是为什么它作为Haskell标准库的一部分而受到祝福。

像往常一样,人们不断发明新的,有用的抽象,你可能想看看应用函子,最好的参考可能是一个文件,称为应用编程与效果由康纳麦克布赖德和罗斯派特森。

这里的其他答案是完整的,但我会尝试函数的FP使用的另一种解释。 以此为例:

仿函数是一个types为a的容器,当它经历一个从ab映射的函数时,产生一个btypes的容器。

与C ++中抽象函数指针的使用不同,这里的函数不是函数。 相反,当它受到某个函数的影响时,它的行为是一致

有三个不同的含义,没有太多相关的!

  • 在Ocaml中,它是一个参数化模块。 见手册 。 我认为最好的方法是举例:(写得很快,可能是越野车)

     module type Order = sig type t val compare: t -> t -> bool end;; module Integers = struct type t = int let compare xy = x > y end;; module ReverseOrder = functor (X: Order) -> struct type t = Xt let compare xy = X.compare yx 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 = Xt * Yt let compare (a,b) (c,d) = if X.compare ac then true else if X.compare ca then false else Y.compare bd end;; (* compare lexicographically *) module X = LexicographicOrder (Integers) (Integers);; X.compare (2,3) (4,5);; module LinearSearch = functor (X: Order) -> struct type t = Xt array let find xk = 0 (* some boring code *) end;; module BinarySearch = functor (X: Order) -> struct type t = Xt array let find xk = 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);; 

您现在可以快速添加许多可能的订单,形成新订单的方式,轻松地在它们上执行二进制或线性search。 通用编程FTW。

  • 在像Haskell这样的函数式编程语言中,它意味着可以被“映射”的一些types构造函数(参数化types,如列表,集合)。 准确地说,函数f配备了(a -> b) -> (fa -> fb) 。 这源于类别理论。 您链接到的维基百科文章是这种用法。

     class Functor f where fmap :: (a -> b) -> (fa -> fb) instance Functor [] where -- lists are a functor fmap = map instance Functor Maybe where -- Maybe is option in Haskell fmap f (Just x) = Just (fx) 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 

所以,这是一种特殊的types构造函数,与Ocaml中的函子几乎没有关系!

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

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

如果您了解C ++,请将OCaml函数看作模板。 C ++只有类模板,并且函子以模块规模工作。

函子的一个例子是Map.Make; module StringMap = Map.Make (String);; 构build一个与String-keyed地图一起工作的地图模块。

你不能用多态性来实现像StringMap这样的东西; 你需要对键进行一些假设。 string模块包含完全sorting的stringtypes的操作(比较等),函子将与string模块包含的操作链接。 你可以做类似于面向对象的编程,但是你会有方法间接的开销。

你有很多好的答案。 我会介绍:

在math意义上,函子是代数上的一种特殊函数。 它是将代数映射到另一个代数的最小函数。 函数法则表示“最小”。

有两种方法来看这个。 例如,列表是某种types的函子。 也就是说,给定一个types为“a”的代数,可以生成一个包含“a”types的列表的兼容代数。 (例如:将元素带到包含它的单例列表的映射:f(a)= [a])同样,兼容性的概念由函子法则表示。

另一方面,给定一个函数f“over”atypes(即fa是将函数f应用到atypes的代数的结果),并且从g:a→b函数,我们可以计算一个新的函数F =(fmap g)将fa映射到f b。 简而言之,fmap是将“函数部分”映射到“函子部分”的F的一部分,g是将“代数部分”映射到“代数部分”的函数的一部分。 它需要一个函数,一个函子,一旦完成,它也是一个函子。

似乎不同的语言使用不同的函数的概念,但它们不是。 他们只是使用函子而不是代数。 OCamls有一个模块的代数,在代数上的函数可以让你以“兼容”的方式将新的声明附加到模块上。

Haskell仿函数不是types类。 它是一个满足types类的自由variables的数据types。 如果你愿意深入挖掘数据types的内涵(没有自由variables),那么你可以重新解释一个数据types作为一个基础代数的函子。 例如:

数据F = F Int

与Ints类同构。 因此,作为一个值构造函数,F是一个将Int映射到F Int(一个等价的代数)的函数。 这是一个函子。 另一方面,你不会在这里免费获得fmap。 这就是模式匹配。

函子是以代数相容的方式把代数“附加”到代数的元素上的。

O'Reilly的OCaml书籍在Inria的网站上有一个很好的例子(截止到写这篇文章的时候不幸的是,这个例子很糟糕)。 在本书中,我发现了一个非常相似的例子: OCaml简介(pdf链接) 。 相关部分是仿函数的章节(PDF中的第139页,PDF中的第149页)。

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

他们也有一个模块,实现比较function,以便它不区分大小写的string比较。

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

 module SSet = MakeSet(StringCaseEqual);; 

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

Tobu将函子与C ++中的模板进行了比较,我认为这很适合。

这个问题的最佳答案可以在布伦特·耶尔西(Brent Yorgey)的“Typeclassopedia”中find。

Monad Reader的这个问题包含一个函子的精确定义,以及许多其他概念的定义和图表。 (Monoid,Applicative,Monad和其他概念在函子中被解释和看到)。

http://haskell.org/sitewikihttp://img.dovov.com8/85/TMR-Issue13.pdf

Typeclassopedia的Functor摘录:“一个简单的直觉就是Functor表示某种”容器“,同时能够将一个函数统一应用于容器中的每个元素”

但是,整个typeclassopedia是一个强烈推荐的阅读,这是非常容易的。 在某种程度上,你可以看到types类在这里呈现为一种平行于对象的devise模式,它们给你一个给定行为或能力的词汇。

干杯

这里有一篇关于编程POV中的函数的文章 ,更具体地说是他们在编程语言中的performance 。

一个函数的实际用法在monad中,如果你寻找的话,你可以在monad上find许多教程。

鉴于其他答案,我现在要发布什么,我会说,这是一个相当沉重的词,但无论如何…

有关Haskell中的“函数”一词的含义,请询问GHCi:

 Prelude> :info Functor class Functor f where fmap :: forall a b. (a -> b) -> fa -> fb (GHC.Base.<$) :: forall a b. a -> fb -> fa -- 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中的函数是可以映射的。 另一种说法是函子是可以被视为一个容器,可以被要求使用一个给定的函数来转换它所包含的值。 因此,对于列表, fmapmap一致,对于Maybefmap f (Just x) = Just (fx)fmap f Nothing = Nothing

Functor typeclass小节和Functors,Applicative Functors和Monoid 学习Haskell for Great Good的部分给出了一些这个特定概念有用的例子。 (总结:很多地方!:-))

请注意,任何monad都可以当作函数,事实上,正如Craig Stuntz指出的那样,最经常使用的函子往往是monad … OTOH,有时候可以方便地将一个types作为Functortypes的一个实例而不用费心去做Monad。 (例如,来自Control.ApplicativeZipList的情况,在上述页面之一中提到 )。

用户魏虎在 回答问题的最高评价时询问:

我理解ML函数和Haskell函数,但是缺乏把它们联系在一起的洞察力。 从理论的angular度来看,这两者之间的关系是什么?

注意 :我不知道ML,所以请原谅和纠正任何相关的错误。

我们先假设我们都熟悉'category'和'functor'的定义。

紧凑的答案是“Haskell- F : Hask -> Hask ”是( F : Hask -> Hask )函子F : Hask -> Hask而“ML- F : Hask -> Hask ”是函子G : ML -> ML'

这里, Hask是由Haskelltypes和它们之间的函数组成的类, MLML'也是由ML结构定义的类。

注意 :把Hask一个类别有一些技术问题 ,但是有一些方法。

从类别理论的angular度来看,这意味着Hask functor是Haskelltypes的映射F

 data F a = ... 

以及Haskell函数的map fmap

 instance Functor F where fmap f = ... 

ML几乎是一样的,虽然没有规范的fmap抽象我知道,所以让我们定义一个:

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

那就是f地图ML -types和fmap地图ML functions,所以

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

是一个F: StructA -> StructB函数F: StructA -> StructB

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

让我们定义什么是一个类别?

这是一堆物品!

现在画一个圆点(现在2个点,一个是'a',另一个是'b'),并命名为圆A(类别)。

这个分类是什么?

对象之间的组合和每个对象的标识function。

所以,我们必须在应用我们的Functor之后映射对象并保存构图。

让我们想象'A'是我们的类别,它有对象['a','b'],存在一个态射a – > b

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

比方说函子被称为“也许”

 data Maybe a = Nothing | Just a 

所以,类别'B'看起来像这样。

请画另一个圈子,但是这次用'也许a'和'也许b'而不是'a'和'b'。

一切似乎都很好,所有的对象都被映射了

'a'变成'也许a','b'变成'也许b'。

但问题是我们必须将态射从“a”映射到“b”。

这意味着在'A'中的态射a – > b应该映射到态射'也许a' – >'也许b'

从 – > b的态射被称为f,那么从'可能a' – >'可能b'的态射称为'fmap f'

现在让我们看看在'A'中'f'做了什么函数,看看我们是否可以在'B'

'A'中'f'的函数定义:

 f :: a -> b 

f取a并返回b

'B'中'f'的函数定义:

 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(fx) 

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

我们将函数'f'应用到'a'types的'x'。 “Nothing”的特殊模式匹配来自Functor Maybe的定义。

所以,我们把我们的对象[a,b]和态射[f]从类别'A'映射到类别'B'。

那是Functor!

在这里输入图像说明

不要与以前的理论或math答案相抵触,但函子也是一个对象(在面向对象的编程语言),只有一个方法,并有效地作为一个函数使用。

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

考虑这个例子,首先在Javascript中,它具有一stream的function:

 [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); } 

那么,如果你添加一个具有地图function的集合类,你可以这样做:

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

这使用IntMapFunction的内联子类来创build一个Functor,它与前面的JavaScript示例中的函数等效。

使用函子可以让您以OO语言应用function性技巧。 当然,一些OO语言也直接支持function,所以这不是必需的。

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

KISS:一个仿函数是一个具有映射方法的对象。

JavaScript中的数组实现映射,因此是函子。 Promises,Streams和Trees通常以函数式语言实现地图,当它们做时,它们被认为是函子。 函子的映射方法把它自己的内容,并使用传递给map的转换callback来转换它们中的每一个,并返回一个新的函数,它包含结构作为第一个函子,但是具有转换后的值。

src: https : //www.youtube.com/watch?v= DisD9ftUyCk & feature = youtu.be & t =76

粗略的概述

在函数式编程中, 函数本质上是将一般的一元函数(即具有一个参数的函数)提升为新typesvariables之间的函数的结构。 在普通对象之间编写和维护简单的函数,并使用函子来提升它们,然后在复杂的容器对象之间手动编写函数,会容易得多。 进一步的优点是只写一次纯函数,然后通过不同的函子重新使用它们。

函数的例子包括数组,“可能”和“任一”函数,期货(参见例如https://github.com/Avaq/Fluture )等等。

插图

考虑从姓和名构build完整人名的function。 我们可以将它定义为fullName(firstName, lastName)作为两个参数的函数,但是它们不适用于仅处理一个参数的函数的函子。 为了解决这个问题,我们把所有的参数集中在一个单一的对象name ,现在它变成了函数的单个参数:

 // In JavaScript notation fullName = name => name.firstName + ' ' + name.lastName 

现在如果我们有很多人在一个arrays呢? 我们可以简单地重复使用我们的函数fullName ,而不是通过为数组提供简单的单行代码的map方法:

 fullNameList = nameList => nameList.map(fullName) 

并使用它

 nameList = [ {firstName: 'Steve', lastName: 'Jobs'}, {firstName: 'Bill', lastName: 'Gates'} ] fullNames = fullNameList(nameList) // => ['Steve Jobs', 'Bill Gates'] 

这将工作,每当我们的nameList中的每个条目是提供firstNamelastName属性的对象。 但是如果一些物体不(甚至不是物体)呢? 为了避免错误并使代码更安全,我们可以将对象包装到Maybetypes中(例如https://sanctuary.js.org/#maybe-type ):

 // function to test name for validity isValidName = name => (typeof name === 'object') && (typeof name.firstName === 'string') && (typeof name.lastName === 'string') // wrap into the Maybe type maybeName = name => isValidName(name) ? Just(name) : Nothing() 

其中Just(name)是一个仅携带有效名称的容器, Nothing()是用于其他任何内容的特殊值。 现在,我们不用中断(或遗忘)来检查参数的有效性,我们可以简单地使用另一行代码重新使用(提升)原始的fullName函数,同样基于map方法,这次为Maybetypes提供:

 // Maybe Object -> Maybe String maybeFullName = maybeName => maybeName.map(fullName) 

并使用它

 justSteve = maybeName( {firstName: 'Steve', lastName: 'Jobs'} ) // => Just({firstName: 'Steve', lastName: 'Jobs'}) notSteve = maybeName( {lastName: 'SomeJobs'} ) // => Nothing() steveFN = maybeFullName(justSteve) // => Just('Steve Jobs') notSteveFN = maybeFullName(notSteve) // => Nothing() 

分类理论

类别理论中的函子是两个类别之间关于其态射构成的映射。 在计算机语言中 ,感兴趣的主要类别是其对象types (某些值集合)的类别 ,其态射是从一个typesa到另一个typesb函数f:a->b b

例如,取aStringtypes, b是数字types, f是将string映射到其长度的函数:

 // f :: String -> Number f = str => str.length 

这里a = String表示所有string的集合, b = Number表示所有数字的集合。 从这个意义上讲, ab代表Set类中的对象(与types的types密切相关,这里的区别是非必要的)。 在集合类中,两组之间的态射恰恰是从第一组到第二组的所有函数。 所以我们这里的长度函数f是从string集合到数字集合中的态射。

由于我们只考虑了设定的范畴,所以相关的函子本身就是将物体和物体发射到态射的映射,它们满足一定的代数律。

示例: Array

Array可能意味着很多事情,但只有一件事情是一个Functor – types构造,将atypes映射到atypes的所有数组的types[a] 。 例如, Array函数将typesString映射到[String] (任意长度的所有string数组)的集合,并将Numbertypes设置为相应的types[Number] (所有数组数组)。

不要混淆Functor图很重要

 Array :: a => [a] 

与态射a -> [a] 。 函子简单地将typesa映射(关联)为types[a] 。 每种types实际上是一组元素,在这里是没有关系的。 相反,态射是这些集合之间的实际函数。 例如,有一种自然的态度(function)

 pure :: a -> [a] pure = x => [x] 

它将值作为单个条目发送到具有该值的1元素数组中。 该函数不是 Array Functor的一部分! 从这个函子的angular度来看, pure就像其他任何函数一样,没有什么特别的。

另一方面, Array函子有它的第二部分 – 态射部分。 它将一个态射f :: a -> b映射到态射[f] :: [a] -> [b]

 // a -> [a] Array.map(f) = arr => arr.map(f) 

这里arr是types为a的任意长度的任意数组,而arr.map(f)是长度相同的数组,其值为typesb ,其条目是将f应用于arr条目的结果。 为了使它成为一个函数,映射到身份和成分组合的身份的math规律必须成立,这很容易检查这个Array例子。

实际上,仿函数意味着在C ++中实现调用操作符的对象。 在ocaml中,我认为functor是指将某个模块作为input输出另一个模块的东西。

简而言之,函子或函数对象是一个类对象,可以像函数一样调用它。

在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! 

是什么让这些如此伟大的是,你可以保持在课堂上的状态 – 想象如果你想问一个函数被调用了多less次。 没有办法以一种整洁,封装的方式做到这一点。 有了一个函数对象,它就像任何其他类一样:你可以在operator ()增加一些实例variables,并且有一些方法来检查这个variables,并且一切都很简单。

Functor与函数式编程没有特别的关系。 它只是一个函数或某种对象的“指针”,可以被称为函数。