在纯函数语言中,是否有一种算法可以获取逆函数?


100

在像Haskell这样的纯函数式语言中,是否有一种算法可以在双射的情况下获得函数(编辑)的逆函数?有没有一种具体的方法可以对您的函数进行编程?


5
从数学上讲,在的情况下f x = 1,1的倒数是一组整数,而其他任何东西的倒数都是一个空集是没有错的。不管有什么答案,功能不是双射的不是最大的问题。
KarolisJuodelė2012年

2
正确的回答是“是”,但是效率不高。令f:A-> B和A有限,然后,给定b€B,您“仅”必须检查所有f(A)来找到所有f(a)= b的a€A。在量子计算机中,可能具有O(size(a))复杂度。当然,您正在寻找一种实用的算法。它不是(具有O(2 ^ size(a))),但是存在...
josejuan 2012年

QuickCheck就是这样做的(他们在f中寻找False:A-> Bool)。
josejuan 2012年

4
@KarolisJuodelė:我不同意;这通常不是逆的意思。几乎每当我遇到该术语时,的逆f函数g就是f . g = idg . f = id。在这种情况下,您的候选人甚至不会进行类型检查。
本·米尔伍德

3
@BenMillwood,您是对的。我所说的是逆像,不是逆函数。我的观点是指出f x = 1没有反函数的答案采用非常狭窄的方法,而忽略了问题的整个复杂性。
KarolisJuodelė12年

Answers:


101

在某些情况下,是的!有一篇名为《免费双向化》的精美论文其中讨论了几种情况-当您的函数足够多态时-可能会完全自动地推导反函数。(它还讨论了在函数不是多态的情况下,使问题难以解决的原因。)

如果您的函数是可逆的,您得到的结果就是逆函数(带有伪输入);在其他情况下,您将获得一个试图“合并”旧输入值和新输出值的函数。


3
这是一篇较新的论文,概述了双向技术的最新发展。它包括三类技术,包括“句法”和基于组合器的方法:iai.uni-bonn.de/~jv/ssgip-bidirectional-final.pdf
sclv 2012年

值得一提的是,在2008年,-cafe收到了此消息,其中有一个恶意骇客,其put功能是将函数转换为任何记录结构Datahaskell.org/pipermail/haskell-cafe/2008-April/042193.html使用类似的方法后来以“免费”的形式呈现(更严格,更普遍,更原则等)。
sclv 2012年

这是2017年,当然还有链接到纸张不再有效这里是更新之一:pdfs.semanticscholar.org/5f0d/...
米娜加布里埃尔

37

不,一般不可能。

证明:考虑类型的双射函数

type F = [Bit] -> [Bit]

data Bit = B0 | B1

假设我们有一个inv :: F -> F这样的逆变器inv f . f ≡ id。说我们已经f = id通过确认功能对它进行了测试

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

由于B0输出中的第一个时间一定是经过一定的时间之后的,因此我们在实际评估测试输入以获得该结果n的深度inv以及可调用的次数上都有一个上限f。现在定义一个功能族

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

显然,对所有人来说0<j≤ng j都是双射,实际上是自反的。所以我们应该能够确认

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

但要实现这一目标,inv (g j)就需要要么

  • 评估g j (B1 : repeat B0)深度n+j > n
  • 评估head $ g j l至少n不同的列表匹配replicate (n+j) B0 ++ B1 : ls

到那时为止,至少其中一个与并g j没有区别f,并且由于inv f未进行任何这些评估,inv因此不可能将其区分开来-除非自己单独进行一些运行时测量,否则这只能在IO Monad

                                                                                                                                   ⬜


19

您可以在Wikipedia(可逆计算)上查找它。

通常,您不能这样做,而且所有功能语言都没有该选项。例如:

f :: a -> Int
f _ = 1

此函数没有反函数。


1
f确实有一个反函数,就是说该反函数是一个不确定函数,这是错误的吗?
马特·芬威克

10
@MattFenwick例如,在类似Haskell的语言中,函数并不是完全不确定的(无需更改类型和使用方式)。即使您可以用数学方法描述逆函数g :: Int -> a,也不存在任何与它的逆函数的Haskell函数。ff
2012年

2
@Matt:在函数式编程和逻辑中查找“底部”。“底部”是“不可能的”值,这是因为它是矛盾的,非终结性的,或者是无法解决的问题的解决方案(这不仅仅是矛盾的,我们可以在探索设计时系统地“追求”解决方案)在开发过程中使用“未定义”和“错误”的空间)。“底部” x具有类型a。它“居住”(或是“值”)每种类型。这是一个逻辑上的矛盾,因为类型是命题,没有任何值可以满足每个命题。在Haskell-Cafe上进行良好的讨论
-nomen

2
@马特:不是用不确定性来描述逆的不存在,而是必须用底数来描述逆。f _ = 1的反数是底部的,因为它必须存在于每种类型中(或者,它是底数,因为f对于具有多个元素的任何类型都没有反函数-我认为您所关注的方面)。触底可以被视为对价值的肯定和否定。可以明智地将任意函数的逆称为“值”底。(即使它不是“真正”的值)
nomen 2012年

1
在很久之后回到这里,我想我明白了Matt的意思:我们经常通过列表对非确定性建模,我们可以对逆进行相同的处理。设f x = 2 * xbe 的逆f' x = [x / 2],然后f _ = 1is 的逆f' 1 = [minBound ..]; f' _ = []。也就是说,有许多1的逆,而没有任何其他值。
amalloy

16

不是在大多数功能语言中,而是在逻辑程序设计或关系程序中,您定义的大多数功能实际上不是功能而是“关系”,并且可以在两个方向上使用。参见例如prolog或kanren。


1
Mercury,它具有Haskell的许多精神。—好点,+ 1。
大约

11

这样的任务几乎总是无法决定的。您可以为某些特定功能提供解决方案,但一般情况下不能。

在这里,您甚至无法识别哪些函数具有逆函数。引用Barendregt,HP Lambda微积分:其语法和语义。阿姆斯特丹北荷兰省(1984)

如果lambda项集既不是空集也不是完整集,则它是不平凡的。如果A和B是在(β)相等下闭合的两个不平凡的不相交的Lambda项集,则A和B是递归不可分割的。

让我们以A为代表可逆函数的lambda项的集合,其余为B。两者都是非空的,并且在beta相等下关闭。因此,不可能确定一个函数是否可逆。

(这适用于无类型的lambda演算。TBH我不知道当我们知道要反转的函数的类型时,该参数是否可以直接适应于有类型的lambda演算。但是我很确定它将是类似。)


11

如果您可以枚举函数的域并可以比较范围内的元素是否相等,则可以-一种相当简单的方法。通过枚举,我的意思是拥有所有可用元素的列表。我会坚持使用Haskell,因为我不了解Ocaml(甚至不知道如何正确将其大写;-)

您想要做的是遍历域中的元素,查看它们是否等于您要求逆的范围的元素,并采用第一个有效的元素:

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

既然您已经说过f是一个双射,那么必然会有一个并且只有一个这样的元素。当然,技巧是确保域的枚举实际上在有限的时间内到达所有元素。如果您尝试将双射从转换IntegerInteger[0,1 ..] ++ [-1,-2 ..]则将无法使用,因为您将永远无法获得负数。具体来说,inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3)永远不会产生价值。

但是,0 : concatMap (\x -> [x,-x]) [1..]它将起作用,因为它将按以下顺序遍历整数[0,1,-1,2,-2,3,-3, and so on]。确实inv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3)及时返回-4

Control.Monad.Omega包可以帮助您通过在一个好办法的元组诸如此类名单刊登; 我敢肯定还有更多这样的软件包-但我不知道它们。


当然,这种方法是低眉毛和蛮力的,更不用说丑陋和低效了!因此,我将在您的问题的最后部分最后谈谈如何“编写”双射。Haskell的类型系统不能证明一个函数是双射的-您确实想要像Agda这样的东西-但它愿意信任您。

(警告:下面是未经测试的代码)

因此,您可以Bijection在类型a和之间定义的数据类型吗b

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

以及尽可能多的常量(您可以在其中说“我知道它们是双射!”),例如:

notBi :: Bi Bool Bool
notBi = Bi not not

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

和几个智能组合器,例如:

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)

我认为您可以做到invert (mapBi add1Bi) [1,5,6]并得到[0,4,5]。如果您以一种聪明的方式选择组合器,我认为您必须Bi手动编写一个常数的次数可能会非常有限。

毕竟,如果您知道一个函数是一个双射,那么您希望对这个事实有所了解,Curry-Howard同构应该可以将其转化为程序:-)


6

我最近一直在处理这样的问题,不,我想说(a)在很多情况下并不困难,但(b)根本没有效率。

基本上,假设您有f :: a -> b,这f的确是一个障碍。您可以f' :: b -> a以非常愚蠢的方式计算逆:

import Data.List

-- | Class for types whose values are recursively enumerable.
class Enumerable a where
    -- | Produce the list of all values of type @a@.
    enumerate :: [a]

 -- | Note, this is only guaranteed to terminate if @f@ is a bijection!
invert :: (Enumerable a, Eq b) => (a -> b) -> b -> Maybe a
invert f b = find (\a -> f a == b) enumerate

如果f是双射并enumerate真正产生的所有值a,那么您最终会击中a这样一个那个f a == b

可以简单地创建具有Bounded和的Enum实例的类型RecursivelyEnumerableEnumerable也可以制作成对的类型Enumerable

instance (Enumerable a, Enumerable b) => Enumerable (a, b) where
    enumerate = crossWith (,) enumerate enumerate

crossWith :: (a -> b -> c) -> [a] -> [b] -> [c]
crossWith f _ [] = []
crossWith f [] _ = []
crossWith f (x0:xs) (y0:ys) =
    f x0 y0 : interleave (map (f x0) ys) 
                         (interleave (map (flip f y0) xs)
                                     (crossWith f xs ys))

interleave :: [a] -> [a] -> [a]
interleave xs [] = xs
interleave [] ys = []
interleave (x:xs) ys = x : interleave ys xs

Enumerable类型的析取也是如此:

instance (Enumerable a, Enumerable b) => Enumerable (Either a b) where
    enumerate = enumerateEither enumerate enumerate

enumerateEither :: [a] -> [b] -> [Either a b]
enumerateEither [] ys = map Right ys
enumerateEither xs [] = map Left xs
enumerateEither (x:xs) (y:ys) = Left x : Right y : enumerateEither xs ys

我们既可以为之(,)Either可能做到这一点,这意味着我们可以为任何代数数据类型做到这一点。


5

并非每个函数都有逆函数。如果将讨论限制为一对一功能,则反转任意功能的能力将使您能够破解任何密码系统。我们有点希望这是不可行的,即使在理论上也是如此!


13
任何密码系统(不包括一些奇怪的密码系统,例如一个时间垫,由于其他原因是不可行的)可能会被暴力破解。但这并不会降低它们的用处,也不是一个不切实际的昂贵的反转函数。

真的吗 如果您认为加密功能String encrypt(String key, String text)没有密钥,您将仍然无能为力。编辑:加上德尔南所说的。
mck 2012年

@MaciekAlbin取决于您的攻击模型。例如,选择的明文攻击可能允许提取密钥,然后允许攻击使用该密钥加密的其他密文。

我所说的“可行”是指可以在任何合理的时间内完成的事情。我不是说“有争议”(我很确定)。
杰弗里·斯科菲尔德

@JeffreyScofield我明白你的意思了。但是我不得不说,我对“理论上可行”感到困惑-(我们对可行性的定义)不是仅指实际操作的难度吗?

5

在某些情况下,可以通过将双射函数转换为符号表示来求逆。基于此示例,我编写了此Haskell程序来查找一些简单的多项式函数的逆:

bijective_function x = x*2+1

main = do
    print $ bijective_function 3
    print $ inverse_function bijective_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

simplify X = X
simplify (Div (Const a) (Const b)) = Const (a/b)
simplify (Mult (Const a) (Const b)) | a == 0 || b == 0 = 0 | otherwise = Const (a*b)
simplify (Negate (Negate a)) = simplify a
simplify (Subtract a b) = simplify ( Plus (simplify a) (Negate (simplify b)) )
simplify (Div a b) | a == b = Const 1.0 | otherwise = simplify (Div (simplify a) (simplify b))
simplify (Mult a b) = simplify (Mult (simplify a) (simplify b))
simplify (Const a) = Const a
simplify (Plus (Const a) (Const b)) = Const (a+b)
simplify (Plus a (Const b)) = simplify (Plus (Const b) (simplify a))
simplify (Plus (Mult (Const a) X) (Mult (Const b) X)) = (simplify (Mult (Const (a+b)) X))
simplify (Plus (Const a) b) = simplify (Plus (simplify b) (Const a))
simplify (Plus X a) = simplify (Plus (Mult 1 X) (simplify a))
simplify (Plus a X) = simplify (Plus (Mult 1 X) (simplify a))
simplify (Plus a b) = (simplify (Plus (simplify a) (simplify b)))
simplify a = a

inverse X = X
inverse (Const a) = simplify (Const a)
inverse (Mult (Const a) (Const b)) = Const (a * b)
inverse (Mult (Const a) X) = (Div X (Const a))
inverse (Plus X (Const a)) = (Subtract X (Const a))
inverse (Negate x) = Negate (inverse x)
inverse a = inverse (simplify a)

inverse_function x = with_function inverse x

该示例仅适用于算术表达式,但可能可以推广为也适用于列表。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.