在像Haskell这样的纯函数式语言中,是否有一种算法可以在双射的情况下获得函数(编辑)的逆函数?有没有一种具体的方法可以对您的函数进行编程?
f
函数g
就是f . g = id
和g . f = id
。在这种情况下,您的候选人甚至不会进行类型检查。
f x = 1
没有反函数的答案采用非常狭窄的方法,而忽略了问题的整个复杂性。
在像Haskell这样的纯函数式语言中,是否有一种算法可以在双射的情况下获得函数(编辑)的逆函数?有没有一种具体的方法可以对您的函数进行编程?
f
函数g
就是f . g = id
和g . f = id
。在这种情况下,您的候选人甚至不会进行类型检查。
f x = 1
没有反函数的答案采用非常狭窄的方法,而忽略了问题的整个复杂性。
Answers:
在某些情况下,是的!有一篇名为《免费双向化》的精美论文!其中讨论了几种情况-当您的函数足够多态时-可能会完全自动地推导反函数。(它还讨论了在函数不是多态的情况下,使问题难以解决的原因。)
如果您的函数是可逆的,您得到的结果就是逆函数(带有伪输入);在其他情况下,您将获得一个试图“合并”旧输入值和新输出值的函数。
put
功能是将函数转换为任何记录结构Data
:haskell.org/pipermail/haskell-cafe/2008-April/042193.html使用类似的方法后来以“免费”的形式呈现(更严格,更普遍,更原则等)。
不,一般不可能。
证明:考虑类型的双射函数
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≤n
,g 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
。
⬜
f
确实有一个反函数,就是说该反函数是一个不确定函数,这是错误的吗?
g :: Int -> a
,也不存在任何与它的逆函数的Haskell函数。f
f
f x = 2 * x
be 的逆f' x = [x / 2]
,然后f _ = 1
is 的逆f' 1 = [minBound ..]; f' _ = []
。也就是说,有许多1的逆,而没有任何其他值。
这样的任务几乎总是无法决定的。您可以为某些特定功能提供解决方案,但一般情况下不能。
在这里,您甚至无法识别哪些函数具有逆函数。引用Barendregt,HP Lambda微积分:其语法和语义。阿姆斯特丹北荷兰省(1984):
如果lambda项集既不是空集也不是完整集,则它是不平凡的。如果A和B是在(β)相等下闭合的两个不平凡的不相交的Lambda项集,则A和B是递归不可分割的。
让我们以A为代表可逆函数的lambda项的集合,其余为B。两者都是非空的,并且在beta相等下关闭。因此,不可能确定一个函数是否可逆。
(这适用于无类型的lambda演算。TBH我不知道当我们知道要反转的函数的类型时,该参数是否可以直接适应于有类型的lambda演算。但是我很确定它将是类似。)
如果您可以枚举函数的域并可以比较范围内的元素是否相等,则可以-一种相当简单的方法。通过枚举,我的意思是拥有所有可用元素的列表。我会坚持使用Haskell,因为我不了解Ocaml(甚至不知道如何正确将其大写;-)
您想要做的是遍历域中的元素,查看它们是否等于您要求逆的范围的元素,并采用第一个有效的元素:
inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]
既然您已经说过f
是一个双射,那么必然会有一个并且只有一个这样的元素。当然,技巧是确保域的枚举实际上在有限的时间内到达所有元素。如果您尝试将双射从转换Integer
为Integer
,[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同构应该可以将其转化为程序:-)
我最近一直在处理这样的问题,不,我想说(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
实例的类型RecursivelyEnumerable
。Enumerable
也可以制作成对的类型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
可能做到这一点,这意味着我们可以为任何代数数据类型做到这一点。
在某些情况下,可以通过将双射函数转换为符号表示来求逆。基于此示例,我编写了此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
该示例仅适用于算术表达式,但可能可以推广为也适用于列表。
f x = 1
,1的倒数是一组整数,而其他任何东西的倒数都是一个空集是没有错的。不管有什么答案,功能不是双射的不是最大的问题。