Álgebras-F e barras-carvão são estruturas matemáticas que são instrumentais no raciocínio sobre tipos indutivos (ou recursivos ).
Álgebras F
Vamos começar primeiro com álgebras F. Vou tentar ser o mais simples possível.
Eu acho que você sabe o que é um tipo recursivo. Por exemplo, este é um tipo para uma lista de números inteiros:
data IntList = Nil | Cons (Int, IntList)
É óbvio que é recursivo - de fato, sua definição se refere a si mesma. Sua definição consiste em dois construtores de dados, que têm os seguintes tipos:
Nil :: () -> IntList
Cons :: (Int, IntList) -> IntList
Note que eu escrevi o tipo de Nilas () -> IntList, não simplesmente IntList. De fato, são tipos equivalentes do ponto de vista teórico, porque o ()tipo tem apenas um habitante.
Se escrevermos assinaturas dessas funções de uma maneira mais teórica, obteremos
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
onde 1é um conjunto de unidades (conjunto com um elemento) e A × Boperação é um produto cruzado de dois conjuntos Ae B(ou seja, conjunto de pares (a, b)onde apassa por todos os elementos de Ae bpassa por todos os elementos de B).
União disjunta de dois conjuntos Ae Bé um conjunto A | Bque é uma união de conjuntos {(a, 1) : a in A}e {(b, 2) : b in B}. Essencialmente, é um conjunto de todos os elementos de ambos Ae B, mas com cada um desses elementos 'marcados' como pertencentes a um Aou outro B, portanto, quando escolhermos qualquer elemento, A | Bsaberemos imediatamente se esse elemento veio Aou não B.
Podemos 'juntar-se' Nile Consfunções, para que eles formem uma única função trabalhando em um conjunto 1 | (Int × IntList):
Nil|Cons :: 1 | (Int × IntList) -> IntList
De fato, se a Nil|Consfunção é aplicada ao ()valor (que obviamente pertence ao 1 | (Int × IntList)conjunto), ela se comporta como se fosse Nil; se Nil|Consfor aplicado a qualquer valor do tipo (Int, IntList)(esses valores também estão no conjunto 1 | (Int × IntList), ele se comporta como Cons.
Agora considere outro tipo de dados:
data IntTree = Leaf Int | Branch (IntTree, IntTree)
Possui os seguintes construtores:
Leaf :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
que também pode ser associado a uma função:
Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
Pode-se ver que essas duas joinedfunções têm um tipo semelhante: ambas se parecem
f :: F T -> T
onde Fé um tipo de transformação que leva o nosso tipo e dá tipo mais complexo, que consiste em xe |operações, usos Te possivelmente outros tipos. Por exemplo, para IntListe IntTree Ftem a seguinte aparência:
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
Podemos notar imediatamente que qualquer tipo algébrico pode ser escrito dessa maneira. De fato, é por isso que eles são chamados de 'algébricos': eles consistem em um número de 'somas' (uniões) e 'produtos' (produtos cruzados) de outros tipos.
Agora podemos definir álgebra F. A álgebra F é apenas um par (T, f), onde Té algum tipo e fé uma função do tipo f :: F T -> T. Nos nossos exemplos, as álgebras F são (IntList, Nil|Cons)e (IntTree, Leaf|Branch). Observe, no entanto, que, apesar desse tipo de ffunção, é o mesmo para cada F, Te felas próprias podem ser arbitrárias. Por exemplo, (String, g :: 1 | (Int x String) -> String)ou (Double, h :: Int | (Double, Double) -> Double)para alguns, ge htambém são álgebras F para F. correspondente
Posteriormente, podemos introduzir homomorfismos da álgebra F e, em seguida , álgebras F iniciais , que possuem propriedades muito úteis. De fato, (IntList, Nil|Cons)é uma álgebra F1 inicial e (IntTree, Leaf|Branch)é uma álgebra F2 inicial. Não apresentarei definições exatas desses termos e propriedades, pois são mais complexos e abstratos do que o necessário.
No entanto, o fato de, digamos, (IntList, Nil|Cons)ser álgebra F, permite definir foldfunções semelhantes a esse tipo. Como você sabe, fold é um tipo de operação que transforma algum tipo de dados recursivo em um valor finito. Por exemplo, podemos dobrar uma lista de números inteiros em um único valor, que é uma soma de todos os elementos da lista:
foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
É possível generalizar essa operação em qualquer tipo de dados recursivo.
A seguir, é uma assinatura da foldrfunção:
foldr :: ((a -> b -> b), b) -> [a] -> b
Observe que usei chaves para separar os dois primeiros argumentos do último. Essa não é uma foldrfunção real , mas é isomórfica (ou seja, você pode facilmente obter uma da outra e vice-versa). Parcialmente aplicado foldrterá a seguinte assinatura:
foldr ((+), 0) :: [Int] -> Int
Podemos ver que essa é uma função que pega uma lista de números inteiros e retorna um único número inteiro. Vamos definir essa função em termos do nosso IntListtipo.
sumFold :: IntList -> Int
sumFold Nil = 0
sumFold (Cons x xs) = x + sumFold xs
Vemos que essa função consiste em duas partes: a primeira parte define o comportamento dessa função em Nilparte IntListe a segunda parte define o comportamento da função em Consparte.
Agora, suponha que não estamos programando em Haskell, mas em alguma linguagem que permita o uso de tipos algébricos diretamente nas assinaturas de tipo (bem, tecnicamente, o Haskell permite o uso de tipos algébricos por tuplas e Either a btipos de dados, mas isso levará a verbosidade desnecessária). Considere uma função:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
Pode-se ver que reductoré uma função do tipo F1 Int -> Int, assim como na definição da álgebra F! De fato, o par (Int, reductor)é uma álgebra F1.
Como IntListé uma álgebra F1 inicial, para cada tipo Te função r :: F1 T -> Texiste uma função chamada catamorfismo para r, que se converte IntListem T, e essa função é única. De fato, em nosso exemplo um catamorphism para reductoré sumFold. Observe como reductore sumFoldsão semelhantes: eles têm quase a mesma estrutura! Na reductordefinição, o suso do parâmetro (do tipo corresponde a T) corresponde ao uso do resultado da computação de sumFold xsna sumFolddefinição.
Apenas para torná-lo mais claro e ajudá-lo a ver o padrão, aqui está outro exemplo, e começamos novamente a partir da função de dobra resultante. Considere a appendfunção que anexa seu primeiro argumento ao segundo:
(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
É assim que fica no nosso IntList:
appendFold :: IntList -> IntList -> IntList
appendFold ys () = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
Novamente, vamos tentar escrever o redutor:
appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys () = ys
appendReductor ys (x, rs) = x : rs
appendFoldé um catamorphism para appendReductorque transforma IntListem IntList.
Portanto, essencialmente, as álgebras F permitem definir 'dobras' em estruturas de dados recursivas, ou seja, operações que reduzem nossas estruturas a algum valor.
F-coalgebras
F-coalgebras são os chamados termos 'duplos' para álgebras F. Eles nos permitem definir unfoldstipos de dados recursivos, ou seja, uma maneira de construir estruturas recursivas a partir de algum valor.
Suponha que você tenha o seguinte tipo:
data IntStream = Cons (Int, IntStream)
Este é um fluxo infinito de números inteiros. Seu único construtor tem o seguinte tipo:
Cons :: (Int, IntStream) -> IntStream
Ou, em termos de conjuntos
Cons :: Int × IntStream -> IntStream
O Haskell permite padronizar a correspondência nos construtores de dados, para que você possa definir as seguintes funções trabalhando em IntStreams:
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
Naturalmente, você pode 'unir' essas funções em uma única função do tipo IntStream -> Int × IntStream:
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
Observe como o resultado da função coincide com a representação algébrica do nosso IntStreamtipo. O mesmo pode ser feito para outros tipos de dados recursivos. Talvez você já tenha notado o padrão. Estou me referindo a uma família de funções do tipo
g :: T -> F T
Onde Testá algum tipo. A partir de agora vamos definir
F1 T = Int × T
Agora, F-coalgebra é um par (T, g), onde Té um tipo e gé uma função do tipo g :: T -> F T. Por exemplo, (IntStream, head&tail)é um F1-coalgebra. Novamente, assim como nas álgebras F, ge Tpode ser arbitrário, por exemplo, (String, h :: String -> Int x String)também é uma álgebra F1 por alguns h.
Entre todas as barras de carvão F, existem as chamadas barras de terminal F , que são duplas às álgebras F iniciais. Por exemplo, IntStreamé um terminal F-coalgebra. Isso significa que, para todo tipo Te função p :: T -> F1 T, existe uma função chamada anamorfismo , que se converte Tem IntStream, e essa função é única.
Considere a seguinte função, que gera um fluxo de números inteiros sucessivos a partir do dado:
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
Agora vamos inspecionar uma função natsBuilder :: Int -> F1 Int, ou seja natsBuilder :: Int -> Int × Int:
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
Novamente, podemos ver alguma semelhança entre natse natsBuilder. É muito semelhante à conexão que observamos anteriormente com redutores e dobras. natsé um anamorfismo para natsBuilder.
Outro exemplo, uma função que pega um valor e uma função e retorna um fluxo de aplicativos sucessivos da função para o valor:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
Sua função de construtor é a seguinte:
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
Então iterateé um anamorfismo para iterateBuilder.
Conclusão
Então, em resumo, as álgebras-F permitem definir dobras, isto é, operações que reduzem a estrutura recursiva em um único valor, e as álgebras-F permitem fazer o oposto: construir uma estrutura [potencialmente infinita] a partir de um único valor.
De fato, as álgebras F e as coalgebras de Haskell coincidem. Esta é uma propriedade muito agradável, que é uma conseqüência da presença do valor 'bottom' em cada tipo. Assim, em Haskell, podem ser criadas dobras e desdobramentos para todos os tipos recursivos. No entanto, o modelo teórico por trás disso é mais complexo do que o que apresentei acima, por isso evitei-o deliberadamente.
Espero que isto ajude.