在纯函数式语言中,是否有算法来获得反函数?

Mai*_*tor 97 ocaml haskell functional-programming clojure

在像Haskell这样的纯函数式语言中,是否存在一个算法来获取函数的逆,当它是双射的时(编辑)?是否有一种特定的方式来编程你的功能呢?

Dan*_*ner 100

在某些情况下,是的!有一篇名为Bidirectionalization for free的漂亮论文!讨论了一些情况 - 当你的函数具有足够的多态性时 - 在可能的情况下,完全自动地导出反函数.(它还讨论了当函数不是多态时,什么使问题变得困难.)

在函数可逆的情况下,你得到的是反函数(带有伪输入); 在其他情况下,您将获得一个尝试"合并"旧输入值和新输出值的函数.

  • 这是一篇更新的论文,调查了双向化的最新技术.它包括三个技术系列,包括"句法"和基于组合器的方法:http://www.iai.uni-bonn.de/~jv/ssgip-bidirectional-final.pdf (3认同)

lef*_*out 35

不,一般来说这是不可能的.

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

type F = [Bit] -> [Bit]
Run Code Online (Sandbox Code Playgroud)

data Bit = B0 | B1
Run Code Online (Sandbox Code Playgroud)

假设我们有一个逆变器inv :: F -> F这样inv f . f ? id.假设我们已经f = id通过确认来测试它的功能

inv f (repeat B0) -> (B0 : ls)
Run Code Online (Sandbox Code Playgroud)

因为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
Run Code Online (Sandbox Code Playgroud)

显然,对于所有人来说0<j?n,g j是一个双射,实际上是自我反转.所以我们应该能够证实

inv (g j) (replicate (n+j) B0 ++ B1 : repeat B0) -> (B1 : ls)
Run Code Online (Sandbox Code Playgroud)

但为了实现这一点,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.

                                                                                                                                   ⬜


mck*_*mck 18

你可以在维基百科上查找它,它叫做可逆计算.

一般来说,你不能这样做,并且没有一种功能语言有这个选项.例如:

f :: a -> Int
f _ = 1
Run Code Online (Sandbox Code Playgroud)

此功能没有反转.

  • @MattFenwick在像Haskell这样的语言中,函数根本不是非确定性的(不改变类型和使用它们的方式).不存在任何Haskell函数`g :: Int - > a`,它是`f`的反函数,即使你可以用数学方法描述`f`的反函数. (10认同)
  • 说“f”确实有反函数是错误的吗?只是反函数是一个非确定性函数? (2认同)
  • @Matt:在函数式编程和逻辑中查找"底部"."底层"是一个"不可能"的价值,要么是因为它是矛盾的,不是终止的,要么解决一个不可判定的问题(这不仅仅是矛盾的 - 我们可以在探索设计时有条不紊地"追逐"解决方案在开发过程中使用"未定义"和"错误"的空间."底部"x具有类型a.它适用于每种类型的"居住"(或"价值").这是一个逻辑矛盾,因为类型是命题,没有价值满足每个命题.看看Haskell-Cafe的讨论 (2认同)
  • @Matt:不是用非确定性来描述逆的不存在,而是必须用底部来表征它.f _ = 1的倒数是底部,因为它必须存在于每种类型中(或者,它是底部,因为f对于具有多于一个元素的任何类型都没有反函数 - 我认为这是您关注的方面).作为关于价值观的断言,作为底部既可以是积极的,也可以是消极的.人们可以合理地将任意函数的逆作为"价值"底部.(即使它不是"真正的"价值) (2认同)

ama*_*loy 16

不是在大多数函数语言中,而是在逻辑编程或关系编程中,您定义的大多数函数实际上不是函数而是"关系",并且这些函数可以在两个方向上使用.参见例如prolog或kanren.


Pet*_*lák 11

像这样的任务几乎总是不可判定的.您可以为某些特定功能提供解决方案,但不是一般的.

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

如果一组lambda-terms既不是空集也不是全集,那么它们是非常重要的.如果A和B是在(β)相等下关闭的两个非平凡的,不相交的lambda项集合,则A和B是递归不可分离的.

让我们将A表示为表示可逆函数的lambda项,将B表示为其余部分.两者都是非空的,并且在beta等式下关闭.因此无法确定某个功能是否可逆.

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


yat*_*975 11

如果你可以枚举函数的域并且可以比较范围的元素是否相等,你可以 - 以一种相当直接的方式.通过枚举我的意思是有一个可用的所有元素的列表.我会坚持Haskell,因为我不知道Ocaml(甚至如何正确地利用它;-)

你想要做的是运行域的元素,看看它们是否等于你想要反转的范围的元素,并采取第一个有效的:

inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]
Run Code Online (Sandbox Code Playgroud)

既然你已经说过这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类型s之间ab:

data Bi a b = Bi {
    apply :: a -> b,
    invert :: b -> a 
}
Run Code Online (Sandbox Code Playgroud)

和你一样多的常数(你可以说'我知道它们是双射!'),如:

notBi :: Bi Bool Bool
notBi = Bi not not

add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)
Run Code Online (Sandbox Code Playgroud)

以及一些智能组合器,例如:

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)
Run Code Online (Sandbox Code Playgroud)

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

毕竟,如果你知道一个函数是一个双射,那么你希望在你的头脑中有一个关于这个事实的证明草图,Curry-Howard同构应该能够变成一个程序:-)


Lui*_*las 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
Run Code Online (Sandbox Code Playgroud)

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

具有BoundedEnum实例的类型可以很简单地进行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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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


Jef*_*eld 5

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

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

And*_*een 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
Run Code Online (Sandbox Code Playgroud)

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