Visão geral aproximada
Na programação funcional, um functor é essencialmente uma construção de elevação de funções unárias comuns (isto é, aquelas com um argumento) para funções entre variáveis de novos tipos. É muito mais fácil escrever e manter funções simples entre objetos simples e usar functors para levantá-los e depois escrever funções manualmente entre objetos contêineres complicados. Outra vantagem é escrever funções simples apenas uma vez e depois reutilizá-las através de diferentes functores.
Exemplos de functores incluem matrizes, "talvez" e "qualquer" functores, futuros (consulte, por exemplo, https://github.com/Avaq/Fluture ) e muitos outros.
Ilustração
Considere a função que constrói o nome completo da pessoa a partir do nome e sobrenome. Poderíamos defini-lo como fullName(firstName, lastName)função de dois argumentos, que, no entanto, não seriam adequados para functores que lidam apenas com funções de um argumento. Para remediar, coletamos todos os argumentos em um único objeto name, que agora se torna o único argumento da função:
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
Agora, e se tivermos muitas pessoas em uma matriz? Em vez de revisar manualmente a lista, podemos simplesmente reutilizar nossa função fullNameatravés do mapmétodo fornecido para matrizes com uma única linha de código curta:
fullNameList = nameList => nameList.map(fullName)
e use-o como
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
Isso funcionará, sempre que cada entrada no nosso nameListfor um objeto que fornece propriedades firstNamee tanto lastName. Mas e se alguns objetos não o fizerem (ou nem sequer são objetos)? Para evitar os erros e tornar o código mais seguro, podemos agrupar nossos objetos no Maybetipo (por exemplo, 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()
onde Just(name)é um contêiner que contém apenas nomes válidos e Nothing()é o valor especial usado para todo o resto. Agora, em vez de interromper (ou esquecer) para verificar a validade de nossos argumentos, podemos simplesmente reutilizar (elevar) nossa fullNamefunção original com outra linha de código única, baseada novamente no mapmétodo, desta vez fornecido para o tipo Maybe:
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
e use-o 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()
Teoria da categoria
Um Functor na Teoria da Categoria é um mapa entre duas categorias, respeitando a composição de seus morfismos. Em uma linguagem de computador , a principal categoria de interesse é aquela cujos objetos são tipos (determinados conjuntos de valores) e cujos morfismos são funções f:a->bde um tipo apara outro tipo b.
Por exemplo, considere aser o Stringtipo, bo tipo Number e fé a função que mapeia uma sequência em seu comprimento:
// f :: String -> Number
f = str => str.length
Aqui a = Stringrepresenta o conjunto de todas as strings e b = Numbero conjunto de todos os números. Nesse sentido, ambos ae brepresentam objetos na Categoria de conjunto (que está intimamente relacionada à categoria de tipos, com a diferença aqui não essencial). Na categoria de conjuntos, os morfismos entre dois conjuntos são precisamente todas as funções do primeiro conjunto ao segundo. Portanto, nossa função de comprimento faqui é um morfismo do conjunto de cordas para o conjunto de números.
Como consideramos apenas a categoria definida, os Functors relevantes a partir dela são mapas que enviam objetos a objetos e morfismos a morfismos, que satisfazem certas leis algébricas.
Exemplo: Array
Arraypode significar muitas coisas, mas apenas uma coisa é um Functor - a construção de tipo, mapeando um tipo ano tipo [a]de todas as matrizes do tipo a. Por exemplo, o Arrayfunctor mapeia o tipo String para o tipo [String](o conjunto de todas as matrizes de cadeias de comprimento arbitrário) e define o tipo Numberpara o tipo correspondente [Number](o conjunto de todas as matrizes de números).
É importante não confundir o mapa do Functor
Array :: a => [a]
com um morfismo a -> [a]. O functor simplesmente mapeia (associa) o tipo ano tipo [a]como uma coisa para outra. Que cada tipo é realmente um conjunto de elementos, não tem relevância aqui. Por outro lado, um morfismo é uma função real entre esses conjuntos. Por exemplo, existe um morfismo natural (função)
pure :: a -> [a]
pure = x => [x]
que envia um valor para a matriz de 1 elemento com esse valor como entrada única. Essa função não faz parte do ArrayFunctor! Do ponto de vista desse functor, pureé apenas uma função como outra qualquer, nada de especial.
Por outro lado, o ArrayFunctor tem sua segunda parte - a parte do morfismo. Que mapeia um morfismo f :: a -> bem um morfismo [f] :: [a] -> [b]:
// a -> [a]
Array.map(f) = arr => arr.map(f)
Aqui arrestá qualquer matriz de comprimento arbitrário com valores do tipo ae arr.map(f)é a matriz do mesmo comprimento com valores do tipo b, cujas entradas são resultados da aplicação fàs entradas de arr. Para torná-lo um functor, as leis matemáticas do mapeamento de identidade para identidade e de composições para composições devem ser válidas, que são fáceis de verificar neste Arrayexemplo.