En la programación funcional, ¿qué es un functor?


224

Me he encontrado con el término 'Functor' varias veces mientras leía varios artículos sobre programación funcional, pero los autores generalmente asumen que el lector ya entiende el término. Mirar en la web ha proporcionado descripciones excesivamente técnicas (vea el artículo de Wikipedia ) o descripciones increíblemente vagas (vea la sección sobre Functores en este sitio web ocaml-tutorial ).

¿Puede alguien definir amablemente el término, explicar su uso y tal vez proporcionar un ejemplo de cómo se crean y usan los Functores?

Editar : Si bien estoy interesado en la teoría detrás del término, estoy menos interesado en la teoría que en la implementación y el uso práctico del concepto.

Edición 2 : Parece que está ocurriendo una terminología cruzada: me refiero específicamente a los Functores de la programación funcional, no a los objetos de función de C ++.



Muy buena respuesta también: stackoverflow.com/a/45149475/1498178
toraritte

Si está más interesado en la implementación práctica y el uso que en la terminología estratosférica y la teoría detrás del concepto, solo necesita un revestimiento: un functor expone una función de "mapa".
Richard Gomes

@RichardGomes En mi humilde opinión, creo que reduce el papel de un functor a una simple interfaz similar a Java, que no lo es. Un functor transforma cosas, crea nuevos tipos a partir de los existentes (en Haskell), lo que significa que los tipos también se asignan. fmapmapea las funciones. Hay dos tipos de mapeos involucrados. Esa forma de ver las cosas ayudará a comprender la teoría de categorías (que es más general). Quiero decir que es interesante entender la teoría básica de categorías para ayudarnos con todas las cosas de la teoría de categorías en Haskell (functor, mónadas, ...).
Ludovic Kuty

@VladtheImpala La publicación del blog es fantástica, pero, incluso si ayuda mucho, me gusta tener en cuenta que un functor construye (asigna) otro tipo. Me gusta especialmente la frase "Un functor F toma cada tipo T y lo asigna a un nuevo tipo FT" en Monads son como burritos . En mi humilde opinión, no es solo un contexto (un recuadro) en torno a un valor, incluso si eso resulta práctico para ver cosas como esta (¿HasVell PoV vs categoría de teoría PoV?)
Ludovic Kuty

Respuestas:


273

La palabra "functor" proviene de la teoría de categorías, que es una rama muy general y muy abstracta de las matemáticas. Ha sido prestado por diseñadores de lenguajes funcionales en al menos dos formas diferentes.

  • En la familia de idiomas ML, un functor es un módulo que toma uno o más módulos como parámetro. Se considera una función avanzada, y la mayoría de los programadores principiantes tienen dificultades con ella.

    Como ejemplo de implementación y uso práctico, podría definir su forma favorita de árbol de búsqueda binaria balanceada de una vez por todas como un functor, y tomaría como parámetro un módulo que proporciona:

    • El tipo de clave que se utilizará en el árbol binario.

    • Una función de pedido total en las teclas

    Una vez que haya hecho esto, puede usar la misma implementación de árbol binario equilibrado para siempre. (El tipo de valor almacenado en el árbol generalmente se deja polimórfico: el árbol no necesita mirar valores distintos a copiarlos, mientras que el árbol definitivamente necesita poder comparar claves, y obtiene la función de comparación de el parámetro del functor)

    Otra aplicación de los functores ML son los protocolos de red en capas . El enlace es a un artículo realmente excelente del grupo CMU Fox; muestra cómo usar los functores para construir capas de protocolo más complejas (como TCP) en tipos de capas más simples (como IP o incluso directamente a través de Ethernet). Cada capa se implementa como un functor que toma como parámetro la capa debajo de ella. La estructura del software en realidad refleja la forma en que las personas piensan sobre el problema, a diferencia de las capas existentes solo en la mente del programador. En 1994, cuando se publicó este trabajo, fue un gran problema.

    Para ver un ejemplo salvaje de los functores de ML en acción, puede ver el ML Module Mania en papel , que contiene un ejemplo publicable (es decir, aterrador) de functors en el trabajo. Para una explicación brillante, clara y lúcida del sistema de módulos ML (con comparaciones con otros tipos de módulos), lea las primeras páginas del brillante papel POPL 1994 de Xavier Leroy Tipos de Manifiesto, Módulos y Compilación Separada .

  • En Haskell, y en algún lenguaje funcional puro relacionado, Functorhay una clase de tipo . Un tipo pertenece a una clase de tipo (o más técnicamente, el tipo "es una instancia de" la clase de tipo) cuando el tipo proporciona ciertas operaciones con cierto comportamiento esperado. Un tipo Tpuede pertenecer a la clase Functorsi tiene cierto comportamiento de colección:

    • El tipo Tse parametriza sobre otro tipo, que debe considerar como el tipo de elemento de la colección. El tipo de la colección completa es entonces algo así como T Int, T String, T Bool, si se contiene números enteros, cadenas o Booleanos respectivamente. Si el tipo de elemento es desconocido, se escribe como un parámetro de tipo a , como en T a.

      Los ejemplos incluyen listas (cero o más elementos de tipo a), el Maybetipo (cero o uno de los elementos de tipo a), conjuntos de elementos de tipo a, matrices de elementos de tipo a, todo tipo de árboles de búsqueda que contienen valores de tipo ay muchos otros se me ocurre.

    • La otra propiedad que Ttiene que satisfacer es que si tiene una función de tipo a -> b(una función en elementos), entonces debe poder tomar esa función y producir una función relacionada en colecciones. Lo hace con el operador fmap, que es compartido por cada tipo en la Functorclase de tipo. El operador está realmente sobrecargado, por lo que si tiene una función evencon tipo Int -> Bool, entonces

      fmap even

      es una función sobrecargada que puede hacer muchas cosas maravillosas:

      • Convierta una lista de enteros en una lista de booleanos

      • Convierta un árbol de enteros en un árbol de booleanos

      • Convertir Nothinga Nothingy Just 7paraJust False

      En Haskell, esta propiedad se expresa dando el tipo de fmap:

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

      donde ahora tenemos un pequeño t, que significa "cualquier tipo en la Functorclase".

    Para resumir, en Haskell un functor es un tipo de colección para la que si se le asigna una función sobre elementos, fmaple devolverá una función sobre colecciones . Como puede imaginar, esta es una idea que puede reutilizarse ampliamente, por lo que es bendecida como parte de la biblioteca estándar de Haskell.

Como de costumbre, las personas continúan inventando nuevas abstracciones útiles, y es posible que desee buscar functores aplicativos , para los cuales la mejor referencia puede ser un artículo llamado Programación Aplicativa con Efectos por Conor McBride y Ross Paterson.


77
Entiendo tanto a ML-functors como a Haskell-functors, pero carece de la perspicacia para relacionarlos. ¿Cuál es la relación entre estos dos, en un sentido teórico de categoría?
Wei Hu

66
@Wei Hu: La teoría de la categoría nunca ha tenido ningún sentido para mí. Lo mejor que puedo decir es que las tres nociones involucran mapeo.
Norman Ramsey el

16
De acuerdo con este wiki de Haskell: en.wikibooks.org/wiki/Haskell/Category_theory , es así: una categoría es una colección de objetos y morfismos (funciones), donde los morfismos son de objetos en una categoría a otros objetos en esa categoría . Un functor es una función que asigna objetos y morfismos de una categoría a objetos y morfismos en otra. Al menos así es como lo entiendo. Lo que eso significa exactamente para la programación aún no lo he entendido.
Paul

55
@ norman-ramsey, ¿has mirado las Matemáticas conceptuales de Lawvere y Schanuel? Soy un novato total en el área, pero el libro es sumamente legible y, me atrevo a decir, agradable. (Me encantó su explicación.)
Ram Rajamony

2
then you have to be able to take that function and product a related function on collections¿Quiso decir en producelugar de product?
problemofficer

64

Otras respuestas aquí están completas, pero intentaré otra explicación del uso de FP del functor . Toma esto como analogía:

Un functor es un contenedor de tipo a que, cuando está sujeto a una función que se asigna a partir de ab , produce un contenedor de tipo b .

A diferencia del uso del puntero de función abstraída en C ++, aquí el functor no es la función; más bien, es algo que se comporta de manera consistente cuando está sujeto a una función .


3
Un contenedor de tipo b significa "el mismo tipo de contenedor que el contenedor de entrada, pero ahora lleno de b". Entonces, si tenemos una lista de plátanos y mapeamos una función que toma un plátano y produce una ensalada de frutas, ahora tenemos una lista de ensaladas de frutas. Del mismo modo, si tuviéramos un árbol de plátanos y asignamos la misma función, ahora tendríamos un árbol de manzanas. Etc. tree y list son dos Functores aquí.
Qqwy

3
"Un functor es un contenedor de tipo a que, cuando está sujeto a una función" - en realidad es al revés - la función (morfismo) está sujeta a un functor, para ser mapeado en otro morfismo
Dmitri Zaitsev

38

¡Hay tres significados diferentes, no muy relacionados!

  • En Ocaml es un módulo parametrizado. Ver manual . Creo que la mejor manera de asimilarlos es con un ejemplo: (escrito rápidamente, podría tener errores)

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

Ahora puede agregar rápidamente muchas órdenes posibles, formas de formar nuevas órdenes, hacer una búsqueda binaria o lineal fácilmente sobre ellas. Programación genérica FTW.

  • En lenguajes de programación funcionales como Haskell, significa algunos constructores de tipos (tipos parametrizados como listas, conjuntos) que se pueden "mapear". Para ser precisos, un functor festá equipado con (a -> b) -> (f a -> f b). Esto tiene orígenes en la teoría de categorías. El artículo de Wikipedia al que se vinculó es este uso.

    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

Por lo tanto, este es un tipo especial de constructores tipo, ¡y tiene poco que ver con los functors en Ocaml!

  • En lenguajes imperativos, es un puntero para funcionar.

¿No debería <q> map </q> en las últimas 3 líneas de este comentario ser <q> fmap </q>?
imz - Ivan Zakharyaschev

1
Siempre he leído que los functores son contenedores, pero esto es solo una simplificación pobre. Su respuesta finalmente proporcionó el eslabón perdido: los functores son una clase de tipo (restricción de tipo) para tipos parametrizados (constructores de tipo). ¡Es así de simple!

16

En OCaml, es un módulo parametrizado.

Si conoce C ++, piense en un functor OCaml como una plantilla. C ++ solo tiene plantillas de clase, y los functores trabajan en la escala del módulo.

Un ejemplo de functor es Map.Make; module StringMap = Map.Make (String);;crea un módulo de mapa que funciona con mapas con clave de cadena.

No podría lograr algo como StringMap con solo polimorfismo; necesita hacer algunas suposiciones sobre las teclas. El módulo String contiene las operaciones (comparación, etc.) en un tipo de cadena totalmente ordenado, y el functor se vinculará con las operaciones que contiene el módulo String. Podría hacer algo similar con la programación orientada a objetos, pero tendría una sobrecarga indirecta del método.


Lo obtuve del sitio web de Ocaml, pero no entiendo cuál sería el uso de un módulo parametrizado.
Erik Forbes

44
@Kornel Sí, lo que describí es un concepto OCaml. El otro concepto es simplemente "valor funcional", que no es nada particular en FP. @Erik me expandí ligeramente, pero los documentos de referencia tardan en cargarse.
Tobu

13

Tienes bastantes buenas respuestas. Voy a lanzar:

Un functor, en el sentido matemático, es un tipo especial de función en un álgebra. Es una función mínima que asigna un álgebra a otro álgebra. La "minimidad" se expresa en las leyes de los functores.

Hay dos maneras de ver esto. Por ejemplo, las listas son functores sobre algún tipo. Es decir, dado un álgebra sobre un tipo 'a', puede generar un álgebra compatible de listas que contengan elementos del tipo 'a'. (Por ejemplo: el mapa que lleva un elemento a una lista singleton que lo contiene: f (a) = [a]) Nuevamente, la noción de compatibilidad se expresa mediante las leyes de los functores.

Por otro lado, dado un functor f "sobre" un tipo a, (es decir, fa es el resultado de aplicar el functor f al álgebra del tipo a), y la función de g: a -> b, podemos calcular un nuevo functor F = (fmap g) que asigna fa a f b. En resumen, fmap es la parte de F que asigna "partes functoras" a "partes functoras", y g es la parte de la función que asigna "partes algebraicas" a "partes algebraicas". Se necesita una función, un functor, y una vez completado, también es un functor.

Puede parecer que diferentes idiomas están utilizando diferentes nociones de functores, pero no lo son. Simplemente están utilizando functores sobre diferentes álgebras. OCamls tiene un álgebra de módulos, y los functores sobre ese álgebra le permiten adjuntar nuevas declaraciones a un módulo de manera "compatible".

Un funk Haskell NO es una clase de tipo. Es un tipo de datos con una variable libre que satisface la clase de tipo. Si está dispuesto a profundizar en las entrañas de un tipo de datos (sin variables libres), puede reinterpretar un tipo de datos como un functor sobre un álgebra subyacente. Por ejemplo:

datos F = F Int

es isomorfo a la clase de Ints. Entonces F, como un constructor de valores, es una función que asigna Int a F Int, un álgebra equivalente. Es un functor. Por otro lado, no obtienes fmap gratis aquí. Para eso sirve la coincidencia de patrones.

Los functores son buenos para "unir" cosas a elementos de álgebras, de una manera algebraicamente compatible.


8

La mejor respuesta a esa pregunta se encuentra en "Typeclassopedia" de Brent Yorgey.

Este número de Monad Reader contiene una definición precisa de lo que es un functor, así como muchas definiciones de otros conceptos y un diagrama. (Monoid, Applicative, Monad y otros conceptos se explican y se ven en relación con un functor).

http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf

Extracto de Typeclassopedia for Functor: "Una intuición simple es que un Functor representa un" contenedor "de algún tipo, junto con la capacidad de aplicar una función uniformemente a cada elemento en el contenedor"

Pero realmente toda la biblioteca de tipos es una lectura altamente recomendada que es sorprendentemente fácil. En cierto modo, puede ver la clase de tipo presentada allí como un patrón paralelo al diseño en el objeto en el sentido de que le dan un vocabulario para el comportamiento o la capacidad dada.

Salud


7

Hay un muy buen ejemplo en el libro O'Reilly OCaml que se encuentra en el sitio web de Inria (que al momento de escribir esto lamentablemente no funciona). Encontré un ejemplo muy similar en este libro utilizado por caltech: Introducción a OCaml (enlace pdf) . La sección relevante es el capítulo sobre functores (página 139 en el libro, página 149 en el PDF).

En el libro tienen un functor llamado MakeSet que crea una estructura de datos que consiste en una lista y funciones para agregar un elemento, determinar si un elemento está en la lista y encontrar el elemento. La función de comparación que se usa para determinar si está o no en el conjunto se ha parametrizado (que es lo que hace que MakeSet sea un functor en lugar de un módulo).

También tienen un módulo que implementa la función de comparación para que haga una comparación de cadenas sin distinción entre mayúsculas y minúsculas.

Usando el functor y el módulo que implementa la comparación, pueden crear un nuevo módulo en una línea:

module SSet = MakeSet(StringCaseEqual);;

que crea un módulo para una estructura de datos establecida que utiliza comparaciones que no distinguen entre mayúsculas y minúsculas. Si quisiera crear un conjunto que utilizara comparaciones entre mayúsculas y minúsculas, simplemente necesitaría implementar un nuevo módulo de comparación en lugar de un nuevo módulo de estructura de datos.

Tobu comparó los functores con las plantillas en C ++, lo que creo que es bastante adecuado.


6

Dadas las otras respuestas y lo que voy a publicar ahora, diría que es una palabra bastante sobrecargada, pero de todos modos ...

Para obtener una pista sobre el significado de la palabra 'functor' en Haskell, pregunte a 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

Entonces, básicamente, un functor en Haskell es algo que se puede mapear. Otra forma de decirlo es que un functor es algo que puede considerarse como un contenedor al que se le puede pedir que use una función dada para transformar el valor que contiene; Por lo tanto, para las listas, fmapcoincide con map, para Maybe, fmap f (Just x) = Just (f x), fmap f Nothing = Nothingetc.

La subsección de la clase de tipo Functor y la sección Functores, Functores aplicativos y Monoides de Learn You a Haskell for Great Good dan algunos ejemplos de dónde es útil este concepto en particular. (Un resumen: ¡muchos lugares! :-))

Tenga en cuenta que cualquier mónada puede ser tratada como un functor, y de hecho, como señala Craig Stuntz, los functores más utilizados tienden a ser mónadas ... OTOH, a veces es conveniente hacer que un tipo sea una instancia de la clase de tipo Functor sin molestarse en convertirlo en una mónada. (Por ejemplo, en el caso de ZipListfrom Control.Applicative, mencionado en una de las páginas mencionadas anteriormente ).


5

Aquí hay un artículo sobre functores de un POV de programación , seguido más específicamente de cómo aparecen en los lenguajes de programación .

El uso práctico de un functor es en una mónada, y puede encontrar muchos tutoriales sobre mónadas si lo busca.


1
"El uso práctico de un functor es en una mónada", no solo. Todas las mónadas son functores, pero hay muchos usos para los que no son mónadas.
amindfv

1
Diría que estudiar mónadas para usar functors es como ahorrar para que un Rolls vaya a comprar comestibles.
Marco Faustinelli

5

En un comentario a la respuesta más votada , el usuario Wei Hu pregunta:

Entiendo tanto a ML-functors como a Haskell-functors, pero carece de la perspicacia para relacionarlos. ¿Cuál es la relación entre estos dos, en un sentido teórico de categoría?

Nota : No sé ML, así que perdona y corrige los errores relacionados.

Asumamos inicialmente que todos estamos familiarizados con las definiciones de 'categoría' y 'functor'.

Una respuesta compacta sería que los "Haskell-functors" son (endo-) functors F : Hask -> Haskmientras que los "ML-functors" son functors G : ML -> ML'.

Aquí, Haskes la categoría formada por los tipos y funciones de Haskell entre ellos, y de manera similar MLy ML'son categorías definidas por las estructuras de ML.

Nota : Existen algunos problemas técnicos con la creación de Haskuna categoría, pero hay formas de evitarlos.

Desde una perspectiva teórica de categoría, esto significa que un Hask-functor es un mapa Fde tipos de Haskell:

data F a = ...

junto con un mapa fmapde funciones de Haskell:

instance Functor F where
    fmap f = ...

ML es más o menos lo mismo, aunque no fmapconozco ninguna abstracción canónica , así que definamos una:

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

Eso es fmapas ML-tipos y fmapmapas ML-funciones, entonces

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

Es functor F: StructA -> StructB.


5

"Functor es el mapeo de objetos y morfismos que preserva la composición e identidad de una categoría".

Vamos a definir qué es una categoría?

¡Es un montón de objetos!

Dibuja algunos puntos (por ahora 2 puntos, uno es 'a' y otro es 'b') dentro de un círculo y nombra ese círculo A (Categoría) por ahora.

¿Qué tiene la categoría?

Composición entre objetos y función de identidad para cada objeto.

Entonces, tenemos que mapear los objetos y preservar la composición después de aplicar nuestro Functor.

Imaginemos que 'A' es nuestra categoría que tiene objetos ['a', 'b'] y existe un morfismo a -> b

Ahora, tenemos que definir un functor que pueda mapear estos objetos y morfismos en otra categoría 'B'.

Digamos que el functor se llama 'Quizás'

data Maybe a = Nothing | Just a

Entonces, la categoría 'B' se ve así.

Dibuje otro círculo pero esta vez con 'Quizás a' y 'Quizás b' en lugar de 'a' y 'b'.

Todo parece bien y todos los objetos están mapeados

'a' se convirtió en 'Quizás a' y 'b' se convirtió en 'Quizás b'.

Pero el problema es que también tenemos que mapear el morfismo de 'a' a 'b'.

Eso significa que el morfismo a -> b en 'A' debería corresponder con el morfismo 'Quizás a' -> 'Quizás b'

el morfismo de a -> b se llama f, luego el morfismo de 'Quizás a' -> 'Quizás b' se llama 'fmap f'

Ahora veamos qué función estaba haciendo 'f' en 'A' y veamos si podemos replicarlo en 'B'

definición de función de 'f' en 'A':

f :: a -> b

f toma a y devuelve b

definición de función de 'f' en 'B':

f :: Maybe a -> Maybe b

f toma Quizás a y devuelve Quizás b

veamos cómo usar fmap para mapear la función 'f' de 'A' a la función 'fmap f' en 'B'

definición de fmap

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

Entonces que hacemos aqui ?

Estamos aplicando la función 'f' a 'x' que es de tipo 'a'. La coincidencia de patrón especial de 'Nothing' proviene de la definición de Functor Maybe.

Entonces, mapeamos nuestros objetos [a, b] y morfismos [f] de la categoría 'A' a la categoría 'B'.

Eso es Functor!

ingrese la descripción de la imagen aquí


Interesante respuesta. Deseo complementarlo con las mónadas, son como burritos (respuesta divertida a la abstracción, la intuición y la "falacia del tutorial de mónada" ) y su oración "Un functor F toma cada tipo T y lo asigna a un nuevo tipo FT", también conocido como constructor de tipos . Programación funcional y teoría de categorías: las categorías y los functores también fueron útiles.
Ludovic Kuty

3

Descripción general

En la programación funcional, un functor es esencialmente una construcción de levantar funciones unarias ordinarias (es decir, aquellas con un argumento) para funciones entre variables de nuevos tipos. Es mucho más fácil escribir y mantener funciones simples entre objetos simples y usar functores para levantarlos, luego escribir funciones manualmente entre objetos contenedores complicados. Otra ventaja es escribir funciones simples solo una vez y luego reutilizarlas a través de diferentes functores.

Los ejemplos de functores incluyen matrices, functores "tal vez" y "cualquiera", futuros (ver, por ejemplo, https://github.com/Avaq/Fluture ) y muchos otros.

Ilustración

Considere la función que construye el nombre completo de la persona a partir del nombre y apellido. Podríamos definirlo fullName(firstName, lastName)como una función de dos argumentos, que, sin embargo, no sería adecuado para los functors que solo se ocupan de las funciones de un argumento. Para remediar, recopilamos todos los argumentos en un solo objeto name, que ahora se convierte en el argumento único de la función:

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

Ahora, ¿qué pasa si tenemos muchas personas en una matriz? En lugar de repasar manualmente la lista, simplemente podemos reutilizar nuestra función a fullNametravés del mapmétodo proporcionado para matrices con una sola línea de código corta:

fullNameList = nameList => nameList.map(fullName)

y úsalo como

nameList = [
    {firstName: 'Steve', lastName: 'Jobs'},
    {firstName: 'Bill', lastName: 'Gates'}
]

fullNames = fullNameList(nameList) 
// => ['Steve Jobs', 'Bill Gates']

Eso funcionará, siempre que cada entrada en nuestro nameListsea ​​un objeto que proporcione propiedades firstNamey lastNamepropiedades. Pero, ¿qué pasa si algunos objetos no lo hacen (o incluso no son objetos)? Para evitar los errores y hacer que el código sea más seguro, podemos envolver nuestros objetos en el Maybetipo (por ejemplo, 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()

donde Just(name)es un contenedor que lleva solo nombres válidos y Nothing()es el valor especial utilizado para todo lo demás. Ahora, en lugar de interrumpir (u olvidar) para verificar la validez de nuestros argumentos, simplemente podemos reutilizar (levantar) nuestra fullNamefunción original con otra sola línea de código, basado nuevamente en el mapmétodo, esta vez provisto para el tipo Quizás:

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

y úsalo como

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

Teoría de la categoría

Un Functor en la teoría de categorías es un mapa entre dos categorías que respeta la composición de sus morfismos. En un lenguaje de computadora , la categoría principal de interés es aquella cuyos objetos son tipos (ciertos conjuntos de valores) y cuyos morfismos son funciones f:a->bde un tipo aa otro b.

Por ejemplo, tome acomo el Stringtipo, bel tipo Número y fes la función que asigna una cadena a su longitud:

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

Aquí a = Stringrepresenta el conjunto de todas las cadenas y b = Numberel conjunto de todos los números. En ese sentido, tanto ay brepresentar objetos en la categoría de conjunto (que está estrechamente relacionado con la categoría de tipos, con la diferencia de que aquí no esencial). En la categoría de conjunto, los morfismos entre dos conjuntos son, precisamente, todas las funciones desde el primer conjunto hasta el segundo. Entonces, nuestra función de longitud faquí es un morfismo del conjunto de cadenas en el conjunto de números.

Como solo consideramos la categoría de conjunto, los Functores relevantes de ella en sí mismos son mapas que envían objetos a objetos y morfismos a morfismos, que satisfacen ciertas leyes algebraicas.

Ejemplo: Array

Arraypuede significar muchas cosas, pero solo una es Functor: la construcción de tipos, que asigna un tipo aal tipo [a]de todas las matrices de tipos a. Por ejemplo, el Arrayfunctor asigna el tipo String al tipo [String](el conjunto de todas las matrices de cadenas de longitud arbitraria) y establece el tipo Numberen el tipo correspondiente [Number](el conjunto de todas las matrices de números).

Es importante no confundir el mapa Functor

Array :: a => [a]

con un morfismo a -> [a]. El functor simplemente asigna (asocia) el tipo aen el tipo [a]como una cosa a otra. Que cada tipo es en realidad un conjunto de elementos, no tiene relevancia aquí. Por el contrario, un morfismo es una función real entre esos conjuntos. Por ejemplo, hay un morfismo natural (función)

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

que envía un valor a la matriz de 1 elemento con ese valor como entrada única. ¡Esa función no es parte del ArrayFunctor! Desde el punto de vista de este functor, purees solo una función como cualquier otra, nada especial.

Por otro lado, el ArrayFunctor tiene su segunda parte: la parte del morfismo. Que mapea un morfismo f :: a -> ben un morfismo [f] :: [a] -> [b]:

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

Aquí arrhay cualquier conjunto de longitud arbitraria con valores de tipo a, y arr.map(f)es el conjunto de la misma longitud con valores de tipo b, cuyas entradas son resultados de aplicar fa las entradas de arr. Para convertirlo en un functor, deben cumplir las leyes matemáticas de mapeo de identidad a identidad y de composiciones a composiciones, que son fáciles de verificar en este Arrayejemplo.


2

No para contradecir las respuestas teóricas o matemáticas anteriores, pero un Functor también es un Objeto (en un lenguaje de programación Orientado a Objetos) que tiene un solo método y se usa efectivamente como una función.

Un ejemplo es la interfaz Runnable en Java, que tiene un solo método: ejecutar.

Considere este ejemplo, primero en Javascript, que tiene funciones de primera clase:

[1, 2, 5, 10].map(function(x) { return x*x; });

Salida: [1, 4, 25, 100]

El método map toma una función y devuelve una nueva matriz con cada elemento como resultado de la aplicación de esa función al valor en la misma posición en la matriz original.

Para hacer lo mismo es Java, utilizando un Functor, primero debe definir una interfaz, por ejemplo:

public interface IntMapFunction {
  public int f(int x);
}

Luego, si agrega una clase de colección que tenía una función de mapa, podría hacer:

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

Esto utiliza una subclase en línea de IntMapFunction para crear un Functor, que es el equivalente OO de la función del ejemplo anterior de JavaScript.

El uso de Functors le permite aplicar técnicas funcionales en un lenguaje OO. Por supuesto, algunos lenguajes OO también tienen soporte para funciones directamente, por lo que no es necesario.

Referencia: http://en.wikipedia.org/wiki/Function_object


En realidad, "objeto de función" no es una descripción correcta de un functor. Por ejemplo, Arrayes un functor, pero Array(value)solo proporciona matrices de 1 elemento.
Dmitri Zaitsev

0

BESO: Un functor es un objeto que tiene un método de mapa.

Las matrices en JavaScript implementan el mapa y, por lo tanto, son functores. Promises, Streams and Trees a menudo implementan mapas en lenguajes funcionales, y cuando lo hacen, se les considera functores. El método de mapa del functor toma sus propios contenidos y transforma cada uno de ellos utilizando la devolución de llamada de transformación pasada al mapa, y devuelve un nuevo functor, que contiene la estructura como el primer functor, pero con los valores transformados.

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


1
Con la nota al margen, "objeto" debe tomarse de manera muy amplia y solo significa "algo". Para los lenguajes OOP, por ejemplo, sustituir objeto por clase . Se podría decir 'un functor es una clase que implementa la interfaz Functor' (Por supuesto, esta interfaz podría no estar físicamente allí, pero podría elevar la lógica del 'mapa' a esa interfaz y hacer que todas sus clases asignables la compartan - siempre y cuando su sistema de escritura permita escribir cosas así de generales, es decir).
Qqwy

1
Creo que las clases son muy confusas para ser sincero, por un lado son solo un modelo para algo concreto / pero también pueden tener métodos (cosas estáticas) y pueden comportarse como objetos. ¿La clase implementa la interfaz o la instancia que crea?
soundyogi

1
Sí, pueden ser confusos. Pero: las clases implementan interfaces ('completan' los espacios en blanco que se dieron en los métodos de las interfaces. En otras palabras: Convierten las pautas abstractas de la interfaz en una guía concreta que se puede instanciar instantáneamente (perdona el juego de palabras). En cuanto a 'las clases se comportan como objetos': en lenguajes verdaderamente orientados a objetos como Ruby, las clases son instancias de la clase 'Clase'. Son tortugas hasta el fondo.
Qqwy

ArrayLa construcción tipo define un solo functor. Sus instancias también se denominan "matrices" pero no son functores. La descripción aquí debe hacerse más precisa.
Dmitri Zaitsev

@DmitriZaitsev ¿Podrías dar más detalles? Entonces, ¿lo que estás diciendo es que las instancias no son functors? No veo el sentido en eso ya que obtienes un nuevo functor mapeando sobre uno.
soundyogi

-4

En la práctica, functor significa un objeto que implementa el operador de llamada en C ++. En ocaml creo que functor se refiere a algo que toma un módulo como entrada y salida de otro módulo.


-6

En pocas palabras, un functor u objeto de función es un objeto de clase que se puede llamar como una función.

En C ++:

Así es como escribes una función

void foo()
{
    cout << "Hello, world! I'm a function!";
}

Así es como escribes un functor

class FunctorClass
{
    public:
    void operator ()
    {
        cout << "Hello, world! I'm a functor!";
    }
};

Ahora puedes hacer esto:

foo(); //result: Hello, World! I'm a function!

FunctorClass bar;
bar(); //result: Hello, World! I'm a functor!

Lo que los hace tan geniales es que puedes mantener el estado en la clase; imagina si quisieras preguntarle a una función cuántas veces se ha llamado. No hay forma de hacer esto de una manera ordenada y encapsulada. Con un objeto de función, es como cualquier otra clase: tendría alguna variable de instancia en la que incremente operator ()y algún método para inspeccionar esa variable, y todo está ordenado como desee.


12
No, esos functores no son la noción de teoría de tipos que utilizan los lenguajes FP.
Tobu

1
Puedo ver cómo se podría probar que FunctorClasscumple con la primera Ley del Fundador, pero ¿podría esbozar una prueba para la segunda Ley? No lo veo del todo.
Jörg W Mittag

3
Bah, ustedes tienen razón. Intenté resolver "la web ha proporcionado descripciones extremadamente técnicas" y sobrepasé, tratando de evitar: "En la familia de lenguajes ML, un functor es un módulo que toma uno o más módulos como parámetro". Esta respuesta, sin embargo, es, bueno, mala. Sobre simplificado y subespecificado. Estoy tentado a ragedelete, pero lo voy a dejar para las generaciones futuras para sacudir la cabeza ante :)
Matt

Me alegra que hayas dejado la respuesta y los comentarios, porque ayuda a enmarcar el problema. ¡Gracias! Tengo problemas porque la mayoría de las respuestas están escritas en términos de Haskell u OCaml, y para mí eso es un poco como explicar los caimanes en términos de cocodrilos.
Rob

-10

Functor no está específicamente relacionado con la programación funcional. Es solo un "puntero" a una función o algún tipo de objeto, que se puede llamar como sería una función.


8
Existe un concepto específico de FP de un functor (de la teoría de categorías), pero tiene razón en que la misma palabra también se usa para otras cosas en lenguajes que no son FP.
Craig Stuntz

¿Está seguro de que los punteros de función son Functores? No veo cómo los punteros de función cumplen las dos Leyes de Functor, especialmente la Ley de Segundo Functor (preservación de la composición del morfismo). ¿Tienes una prueba para eso? (Solo un boceto.)
Jörg W Mittag
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.