В чистых функциональных языках существует ли алгоритм для получения обратной функции?

В чистых функциональных языках, таких как Haskell, есть ли алгоритм, чтобы получить обратную функцию, (редактировать), когда он является биективным? И существует ли конкретный способ программирования вашей функции?

Ответ 1

В некоторых случаях да! Там красивая бумага, называемая Двунаправленность бесплатно!, в которой обсуждается несколько случаев - когда ваша функция достаточно полиморфна - где это возможно, полностью автоматически выводить обратную функцию, (В нем также обсуждается, что затрудняет задачу, когда функции не являются полиморфными.)

Что вы получаете в случае, когда ваша функция обратима, является инверсной (с ложным вводом); в других случаях вы получаете функцию, которая пытается "объединить" старое входное значение и новое выходное значение.

Ответ 2

Нет, это вообще невозможно.

Доказательство. Рассмотрим биективные функции типа

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.

Ответ 3

Вы можете посмотреть его на wikipedia, который называется Реверсивные вычисления.

В общем, вы не можете этого сделать, хотя ни один из функциональных языков не имеет этой опции. Например:

f :: a -> Int
f _ = 1

Эта функция не имеет обратного.

Ответ 4

Не в большинстве функциональных языков, но в логическом программировании или реляционном программировании большинство функций, которые вы определяете, на самом деле не являются функциями, а "отношениями", и они могут использоваться в обоих направлениях. См. Например, пролог или канн.

Ответ 5

Если вы можете перечислить домен функции и можете сравнивать элементы диапазона для равенства, вы можете - довольно простым способом. Перечислим, я имею в виду список всех доступных элементов. Я буду придерживаться 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 вручную, может быть довольно ограниченным.

В конце концов, если вы знаете, что функция - это биекция, вы, надеюсь, имеете наглядный пример этого факта в вашей голове, который изоморфизм Карри-Говарда должен иметь возможность превратиться в программу: -)

Ответ 6

Такие задачи почти всегда неразрешимы. У вас может быть решение для некоторых конкретных функций, но не в целом.

Здесь вы даже не можете распознать, какие функции имеют обратный. Цитирование Barendregt, H. P. Исчисление лямбда: ее синтаксис и семантика. Северная Голландия, Амстердам (1984):

Множество лямбда-термов нетривиально, если оно не является ни пустым, ни полным множеством. Если A и B - два нетривиальных, непересекающихся множества лямбда-членов, замкнутых по (бета) равенству, то A и B рекурсивно неразделимы.

Возьмем A как множество лямбда-членов, представляющих обратимые функции, а B - остальные. Оба являются непустыми и закрытыми по бета-равенству. Поэтому невозможно решить, является ли функция обратимой или нет.

(Это относится к нетипизированному лямбда-исчислению. TBH Я не знаю, может ли аргумент быть непосредственно адаптирован к типизированному лямбда-исчислению, когда мы знаем тип функции, которую мы хотим инвертировать. Но я уверен он будет похож.)

Ответ 7

Недавно я занимался такими проблемами, и нет, я бы сказал, что (а) это не сложно во многих случаях, но (б) оно неэффективно вообще.

В принципе, предположим, что у вас есть f :: a -> b, и что f действительно является bjiection. Вы можете реально вычислить обратный 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 @[email protected]
    enumerate :: [a]

 -- | Note, this is only guaranteed to terminate if @[email protected] 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, вероятно, означает, что мы можем сделать это для любого типа алгебраических данных.

Ответ 8

Не каждая функция имеет обратную. Если вы ограничиваете обсуждение функциями "один-к-одному", способность инвертировать произвольную функцию дает возможность взломать любую криптосистему. Мы вроде бы надеемся, что это невозможно, даже в теории!

Ответ 9

Нет, не все функции имеют инверсии. Например, какова была бы обратная функция этой функции?

f x = 1

Ответ 10

В некоторых случаях можно найти обратную биективную функцию, преобразовав ее в символическое представление. Основываясь на этом примере, я написал эту программу на 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

Этот пример работает только с арифметическими выражениями, но, вероятно, его можно обобщить и для работы со списками.