Se você pode enumerar o domínio da função e pode comparar os elementos do intervalo para igualdade, você pode - de uma maneira bastante direta. Por enumerar, quero dizer ter uma lista de todos os elementos disponíveis. Vou ficar com Haskell, já que não conheço Ocaml (ou mesmo como capitalizá-lo corretamente ;-)
O que você quer fazer é percorrer os elementos do domínio e ver se eles são iguais ao elemento do intervalo que você está tentando inverter e pegar o primeiro que funcione:
inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]
Já que você declarou que f
é uma bijeção, deve haver um e apenas um desses elementos. O truque, é claro, é garantir que sua enumeração do domínio realmente alcance todos os elementos em um tempo finito . Se você está tentando inverter uma bijeção de Integer
para Integer
, o uso [0,1 ..] ++ [-1,-2 ..]
não funcionará, pois você nunca obterá os números negativos. Concretamente, inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3)
nunca renderá um valor.
No entanto, 0 : concatMap (\x -> [x,-x]) [1..]
funcionará, visto que percorre os números inteiros na seguinte ordem [0,1,-1,2,-2,3,-3, and so on]
. Na verdade inv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3)
retorna prontamente -4
!
O pacote Control.Monad.Omega pode ajudá-lo a percorrer listas de tuplas, etc. de uma boa maneira; Tenho certeza de que há mais pacotes como esse - mas não os conheço.
Claro, essa abordagem é bastante simples e bruta, para não mencionar feia e ineficiente! Portanto, terminarei com algumas observações sobre a última parte da sua pergunta, sobre como 'escrever' bijeções. O sistema de tipos de Haskell não prova que uma função é uma bijeção - você realmente quer algo como Agda para isso - mas ele está disposto a confiar em você.
(Aviso: segue código não testado)
Então, você pode definir um tipo de dados de Bijection
s entre os tipos a
e b
:
data Bi a b = Bi {
apply :: a -> b,
invert :: b -> a
}
junto com quantas constantes (onde você pode dizer 'Eu sei que são bijetivas!') quanto desejar, como:
notBi :: Bi Bool Bool
notBi = Bi not not
add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)
e alguns combinadores inteligentes, como:
idBi :: Bi a a
idBi = Bi id id
invertBi :: Bi a b -> Bi b a
invertBi (Bi a i) = (Bi i a)
composeBi :: Bi a b -> Bi b c -> Bi a c
composeBi (Bi a1 i1) (Bi a2 i2) = Bi (a2 . a1) (i1 . i2)
mapBi :: Bi a b -> Bi [a] [b]
mapBi (Bi a i) = Bi (map a) (map i)
bruteForceBi :: Eq b => [a] -> (a -> b) -> Bi a b
bruteForceBi domain f = Bi f (inv domain f)
Acho que você poderia então fazer invert (mapBi add1Bi) [1,5,6]
e obter [0,4,5]
. Se você escolher seus combinadores de maneira inteligente, acho que o número de vezes que terá que escrever uma Bi
constante à mão pode ser bastante limitado.
Afinal, se você sabe que uma função é uma bijeção, você terá um esboço de prova desse fato em sua cabeça, que o isomorfismo de Curry-Howard deve ser capaz de transformar em um programa :-)
f x = 1
, o inverso de 1 é um conjunto de inteiros e o inverso de qualquer outra coisa é um conjunto vazio. Independentemente do que digam algumas respostas, a função não ser bijetiva não é o maior problema.