web-dev-qa-db-fra.com

Dans les langages fonctionnels purs, existe-t-il un algorithme pour obtenir la fonction inverse?

Dans les langages fonctionnels purs comme Haskell, existe-t-il un algorithme pour obtenir l'inverse d'une fonction, (modifier) ​​lorsqu'elle est bijective? Et existe-t-il un moyen spécifique de programmer votre fonction?

91
MaiaVictor

Dans certains cas, oui! Il y a un beau papier appelé Bidirectionalization for Free! qui traite de quelques cas - lorsque votre fonction est suffisamment polymorphe - où il est possible, de manière complètement automatique de dériver une fonction inverse. (Il explique également ce qui rend le problème difficile lorsque les fonctions ne sont pas polymorphes.)

Ce que vous sortez dans le cas où votre fonction est inversible est l'inverse (avec une entrée parasite); dans d'autres cas, vous obtenez une fonction qui essaie de "fusionner" une ancienne valeur d'entrée et une nouvelle valeur de sortie.

90
Daniel Wagner

Non, ce n'est pas possible en général.

Preuve: considérer les fonctions bijectives de type

type F = [Bit] -> [Bit]

avec

data Bit = B0 | B1

Supposons que nous ayons un onduleur inv :: F -> F Tel que inv f . f ≡ id. Supposons que nous l'avons testé pour la fonction f = id, En confirmant que

inv f (repeat B0) -> (B0 : ls)

Étant donné que ce premier B0 Dans la sortie doit être venu après un certain temps, nous avons une limite supérieure n à la fois sur la profondeur à laquelle inv avait effectivement évalué notre entrée de test pour obtenir ce résultat, ainsi que le nombre de fois qu'il a pu appeler f. Définissez maintenant une famille de fonctions

g j (B1 : B0 : ... (n+j times) ... B0 : ls)
   = B0 : ... (n+j times) ... B0 : B1 : ls
g j (B0 : ... (n+j times) ... B0 : B1 : ls)
   = B1 : B0 : ... (n+j times) ... B0 : ls
g j l = l

Clairement, pour tous 0<j≤n, g j Est une bijection, en fait auto-inverse. Nous devrions donc pouvoir confirmer

inv (g j) (replicate (n+j) B0 ++ B1 : repeat B0) -> (B1 : ls)

mais pour y parvenir, inv (g j) aurait dû soit

  • évaluer g j (B1 : repeat B0) à une profondeur de n+j > n
  • évaluer head $ g j l pour au moins n différentes listes correspondant à replicate (n+j) B0 ++ B1 : ls

Jusque-là, au moins l'un des g j Ne peut être distingué de f, et puisque inv f N'avait effectué aucune de ces évaluations, inv pouvait probablement pas fait la différence - à moins de faire quelques mesures d'exécution par lui-même, ce qui n'est possible que dans le IO Monad.

31
leftaroundabout

Vous pouvez le consulter sur wikipedia, il s'appelle Reversible Computing .

En général, vous ne pouvez pas le faire et aucun des langages fonctionnels n'a cette option. Par exemple:

f :: a -> Int
f _ = 1

Cette fonction n'a pas d'inverse.

16
mck

Pas dans la plupart des langages fonctionnels, mais dans la programmation logique ou la programmation relationnelle, la plupart des fonctions que vous définissez ne sont en fait pas des fonctions mais des "relations", et celles-ci peuvent être utilisées dans les deux sens. Voir par exemple prologue ou kanren.

13
amalloy

Si vous pouvez énumérer le domaine de la fonction et comparer les éléments de la plage pour l'égalité, vous pouvez - d'une manière assez simple. Par énumérer, je veux dire avoir une liste de tous les éléments disponibles. Je m'en tiendrai à Haskell, car je ne connais pas Ocaml (ni même comment le capitaliser correctement ;-)

Ce que vous voulez faire, c'est parcourir les éléments du domaine et voir s'ils sont égaux à l'élément de la plage que vous essayez d'inverser, et prendre le premier qui fonctionne:

inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]

Puisque vous avez déclaré que f est une bijection, il y a forcément un et un seul élément de ce type. L'astuce, bien sûr, est de s'assurer que votre énumération du domaine atteint réellement tous les éléments dans un temps fini . Si vous essayez d'inverser une bijection de Integer à Integer, l'utilisation de [0,1 ..] ++ [-1,-2 ..] Ne fonctionnera pas car vous n'obtiendrez jamais les nombres négatifs. Concrètement, inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3) ne donnera jamais de valeur.

Cependant, 0 : concatMap (\x -> [x,-x]) [1..] fonctionnera, car elle parcourt les entiers dans l'ordre suivant [0,1,-1,2,-2,3,-3, and so on]. En effet, inv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3) renvoie rapidement -4!

Le paquet Control.Monad.Omega peut vous aider à parcourir les listes de tuples, etc. d'une bonne manière; Je suis sûr qu'il y a plus de paquets comme ça - mais je ne les connais pas.


Bien sûr, cette approche est plutôt sourde et brutale, sans parler de laide et inefficace! Je terminerai donc par quelques remarques sur la dernière partie de votre question, sur la façon "d'écrire" des bijections. Le système de type de Haskell n'est pas en mesure de prouver qu'une fonction est une bijection - vous voulez vraiment quelque chose comme Agda pour cela - mais il est prêt à vous faire confiance.

(Attention: le code non testé suit)

Vous pouvez donc définir un type de données de Bijection s entre les types a et b:

data Bi a b = Bi {
    apply :: a -> b,
    invert :: b -> a 
}

avec autant de constantes (où vous pouvez dire 'je sais ce sont des bijections!') comme vous le souhaitez, comme:

notBi :: Bi Bool Bool
notBi = Bi not not

add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)

et quelques combinateurs intelligents, tels que:

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)

Je pense que vous pourriez alors faire invert (mapBi add1Bi) [1,5,6] et obtenir [0,4,5]. Si vous choisissez vos combinateurs de manière intelligente, je pense que le nombre de fois que vous devrez écrire une constante Bi à la main pourrait être assez limité.

Après tout, si vous savez qu'une fonction est une bijection, vous aurez, espérons-le, une esquisse de preuve de ce fait dans votre tête, que l'isomorphisme de Curry-Howard devrait pouvoir transformer en programme :-)

9
yatima2975

De telles tâches sont presque toujours indécidables. Vous pouvez avoir une solution pour certaines fonctions spécifiques, mais pas en général.

Ici, vous ne pouvez même pas reconnaître quelles fonctions ont un inverse. Citant Barendregt, H. P. The Lambda Calculus: Its Syntax and Semantics. North Holland, Amsterdam (1984) :

Un ensemble de termes lambda n'est pas trivial s'il n'est ni l'ensemble vide ni l'ensemble complet. Si A et B sont deux ensembles non triviaux et disjoints de termes lambda fermés sous l'égalité (bêta), alors A et B sont récursivement inséparables.

Prenons A pour être l'ensemble des termes lambda qui représentent des fonctions inversibles et B le reste. Les deux sont non vides et fermés sous égalité bêta. Il n'est donc pas possible de décider si une fonction est inversible ou non.

(Cela s'applique au calcul lambda non typé. TBH Je ne sais pas si l'argument peut être directement adapté à un calcul lambda typé lorsque nous connaissons le type d'une fonction que nous voulons inverser. Mais je suis sûr que ce sera similaire.)

8
Petr Pudlák

Toutes les fonctions n'ont pas d'inverse. Si vous limitez la discussion aux fonctions biunivoque, la possibilité d'inverser une fonction arbitraire permet de casser n'importe quel cryptosystème. Nous devons en quelque sorte espérer que ce n'est pas possible, même en théorie!

3
Jeffrey Scofield

Non, toutes les fonctions n'ont même pas d'inverses. Par exemple, quel serait l'inverse de cette fonction?

f x = 1
2
Dirk Holsopple

Dans certains cas, il est possible de trouver l'inverse d'une fonction bijective en la convertissant en une représentation symbolique. Basé sur cet exemple , j'ai écrit ce programme Haskell pour trouver des inverses de quelques fonctions polynomiales simples:

bijective_function x = x*2+1

main = do
    print $ bijective_function 3
    print $ inverse_function bijective_function 3

data Expr = X | Const Double |
            Plus Expr Expr | Subtract Expr Expr | Mult Expr Expr | Div Expr Expr |
            Negate Expr | Inverse Expr |
            Exp Expr | Log Expr | Sin Expr | Atanh Expr | Sinh Expr | Acosh Expr | Cosh Expr | Tan Expr | Cos Expr |Asinh Expr|Atan Expr|Acos Expr|Asin Expr|Abs Expr|Signum Expr|Integer
       deriving (Show, Eq)

instance Num Expr where
    (+) = Plus
    (-) = Subtract
    (*) = Mult
    abs = Abs
    signum = Signum
    negate = Negate
    fromInteger a = Const $ fromIntegral a

instance Fractional Expr where
    recip = Inverse
    fromRational a = Const $ realToFrac a
    (/) = Div

instance Floating Expr where
    pi = Const pi
    exp = Exp
    log = Log
    sin = Sin
    atanh = Atanh
    sinh = Sinh
    cosh = Cosh
    acosh = Acosh
    cos = Cos
    tan = Tan
    asin = Asin
    acos = Acos
    atan = Atan
    asinh = Asinh

fromFunction f = f X

toFunction :: Expr -> (Double -> Double)
toFunction X = \x -> x
toFunction (Negate a) = \a -> (negate a)
toFunction (Const a) = const a
toFunction (Plus a b) = \x -> (toFunction a x) + (toFunction b x)
toFunction (Subtract a b) = \x -> (toFunction a x) - (toFunction b x)
toFunction (Mult a b) = \x -> (toFunction a x) * (toFunction b x)
toFunction (Div a b) = \x -> (toFunction a x) / (toFunction b x)


with_function func x = toFunction $ func $ fromFunction x

inverse X = X
inverse (Const a) = Const a
inverse (Plus (Const a) b) = (Subtract (inverse b) (Const a))
inverse (Plus b (Const a)) = inverse (Plus (Const a) b)
inverse (Mult (Const a) b) = (Div (inverse b) (Const a))
inverse (Mult b (Const a)) = inverse (Mult (Const a) b)
inverse (Negate a) = Negate $ inverse a
inverse (Asin x) = Sin $ inverse x
inverse (Acos x) = Cos $ inverse x
inverse (Atan x) = Tan $ inverse x
inverse_function x = with_function inverse x

Cet exemple ne fonctionne qu'avec des expressions arithmétiques, mais il pourrait probablement être généralisé pour fonctionner également avec des listes.

1
Anderson Green