Чисто в функціональних мовах існує алгоритм отримання зворотної функції?


100

Чи існує в чистих функціональних мовах, як Haskell, алгоритм отримання зворотної функції (редагування), коли вона бієктивна? І чи є певний спосіб запрограмувати свою функцію так, як це?


5
Математично не помиляється сказати, що у випадку з f x = 1оберненою величиною 1 є сукупність цілих чисел, а зворотне все інше - порожнє безліч. Незалежно від того, що кажуть деякі відповіді, функція, яка не є бієктивною, не є найбільшою проблемою.
Karolis Juodelė

2
Правильна відповідь ТАК, але це не ефективно. Нехай f: A -> B і A скінченне, тоді, даючи b € B, ви "тільки" повинні оглянути всі f (A), щоб знайти всі A A, що f (a) = b. У квантовому комп'ютері, можливо, була б складність O (розмір (а)). Звичайно, ви шукаєте практичний алгоритм. Це не (має O (2 ^ розмір (a))), але існує ...
josejuan

QuickCheck робить це точно (вони шукають False в f: A -> Bool).
Жозеджуан

4
@ KarolisJuodelė: Я не згоден; це зазвичай не те, що розуміється під зворотним. Практично щоразу, коли я стикаюся з цим терміном, обернення f- це gтака функція , що f . g = idі g . f = id. Ваш кандидат навіть не вводить прапорець у цьому випадку.
Бен Міллвуд

3
@BenMillwood, ти маєш рацію. Те, що я сказав, називається зворотним зображенням, а не зворотною функцією. Моя думка полягала в тому, що у відповідях, що вказують на те, що f x = 1немає зворотного, дуже вузький підхід і ігнорується вся складність проблеми.
Karolis Juodelė

Відповіді:


101

У деяких випадках так! Існує прекрасна папір під назвою Бідерекціоналізація безкоштовно! де розглядаються декілька випадків - коли ваша функція є достатньо поліморфною - де можливо, повністю автоматично вивести зворотну функцію. (Тут також обговорюється, що ускладнює проблему, коли функції не є поліморфними.)

Те, що ви отримуєте у випадку, коли ваша функція є незворотною, - це зворотне (з помилковим введенням); в інших випадках ви отримуєте функцію, яка намагається "об'єднати" старе вхідне значення і нове вихідне значення.


3
Ось нещодавніший документ, який досліджує сучасний стан бідірекціоналізації. Він включає три родини методів, включаючи "синтаксичний" та підхід на основі комбінаторів: iai.uni-bonn.de/~jv/ssgip-bidirectional-final.pdf
sclv

І лише згадати, що в 2008 році з'явилося це повідомлення -cafe, злим злом для інвертування putфункцій у будь-які структури записів, що походять Data: haskell.org/pipermail/haskell-cafe/2008-April/042193.html, використовуючи підхід, подібний до що подано пізніше (жорсткіше, загальніше, більш принципово тощо) у "безкоштовно".
sclv

Це 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≤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.

                                                                                                                                   ⬜


19

Ви можете подивитися на wikipedia, це називається Реверсивні обчислення" .

Як правило, ви не можете цього зробити, і жодна з функціональних мов не має такої можливості. Наприклад:

f :: a -> Int
f _ = 1

Ця функція не має зворотного.


1
Чи було б неправильним сказати, що fмає зворотну, це просто те, що обернена є недетермінованою функцією?
Метт Фенвік

10
@MattFenwick У таких мовах, як Haskell, наприклад, функції просто не є недетермінованими (без зміни типів та способу їх використання). Не існує жодної функції Haskell, g :: Int -> aяка була б оберненою f, навіть якщо ви можете описати обернено fматематично.
Бен

2
@Matt: Знайдіть "знизу" у функціональному програмуванні та логіці. "Дно" - це "неможливе" значення, або тому, що воно суперечливе, незакінчене, або рішення нерозв'язної проблеми (це більше, ніж просто суперечливе - ми можемо методично "переслідувати" рішення, вивчаючи дизайн простір із використанням "невизначеного" та "помилки" під час розробки). "Нижній" х має тип a. Він "мешкає" (або є "значенням") кожного типу. Це логічне протиріччя, оскільки типи є пропозиціями, і немає значення, яке задовольняє кожну пропозицію. Подивіться на Haskell-Cafe для гарних дискусій
номенклатура

2
@Matt: Замість того, щоб охарактеризувати відсутність інверсів з точки зору недетермінізму, треба охарактеризувати це з точки зору дна. Зворотне значення f _ = 1 - це дно, оскільки воно повинно населяти кожен тип (як альтернатива, воно є дном, оскільки f не має зворотної функції для будь-якого типу з більш ніж одним елементом - я думаю, аспект, на який ви зосереджувались). Бути знизу можна сприймати як позитивно, так і негативно як твердження про цінності. Можна обґрунтовано говорити про зворотну довільну функцію як про "значення" дна. (Незважаючи на те, що це не "насправді" цінність)
номенклатура

1
Покинувшись сюди набагато пізніше, я думаю, що я бачу, до чого потрапляє Метт: ми часто моделюємо недетермінізм за допомогою списків, і ми могли б зробити те ж саме для інверсів. Нехай зворотне f x = 2 * xВЕ f' x = [x / 2], а потім зворотне f _ = 1IS f' 1 = [minBound ..]; f' _ = []. Тобто є багато обертів для 1, а жодна для будь-якого іншого значення.
amalloy

16

Не в більшості функціональних мов, але в логічному програмуванні або реляційному програмуванні більшість визначених вами функцій насправді є не функціями, а "відносинами", і їх можна використовувати в обох напрямках. Дивіться, наприклад, prolog або kanren.


1
Або Меркурій , який інакше поділяє багато духу Хаскелла. - Хороший бал, +1.
близько

11

Такі завдання майже завжди не можна вирішити. Ви можете мати рішення для деяких конкретних функцій, але не в цілому.

Тут ви навіть не можете розпізнати, які функції мають обернену. Цитуючи Barendregt, HP Обчислення Ламбди: його синтаксис та семантика. Північна Голландія, Амстердам (1984) :

Набір лямбда-термінів нетривіальний, якщо він не є ні порожнім, ні повним набором. Якщо A і B є двома нетривіальними, непересічними множинами лямбда-доданків, закритих при (бета) рівності, то A і B рекурсивно невіддільні.

Візьмемо A як набір лямбда-термінів, які представляють зворотні функції, а B - решта. Обидва не порожні та закриті в умовах бета-рівності. Тож неможливо вирішити, функція є зворотною чи ні.

(Це стосується нетипізованого обчислення лямбда. TBH Я не знаю, чи можна аргумент безпосередньо адаптувати до введеного обчислення лямбда, коли ми знаємо тип функції, який ми хочемо інвертувати. Але я впевнений, що це буде подібні.)


11

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

(Попередження: слід неперевірений код)

Отже, чи можна визначити тип даних Bijections між типами 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

Я нещодавно маю справу з подібними проблемами, і ні, я б сказав, що (а) це не складно у багатьох випадках, але (б) це зовсім не ефективно.

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


5

Не кожна функція має зворотну. Якщо ви обмежите обговорення лише функціями один на один, можливість інвертувати довільну функцію надає можливість зламати будь-яку криптосистему. Ми, мабуть, сподіваємось, що це неможливо навіть теоретично!


13
Будь-яка криптосистема (за винятком декількох непарних, наприклад одноразових колодок, нездатних з інших причин) може бути зламана грубою силою. Це не робить їх менш корисними, і це не буде непрактично дорогою функцією інверсії.

Це насправді? якщо ви думаєте про функцію шифрування як String encrypt(String key, String text)без ключа, ви все одно нічого не зможете зробити. EDIT: Плюс те, що сказав Делнан.
mck

@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

Цей приклад працює лише з арифметичними виразами, але він, ймовірно, може бути узагальнений і для роботи зі списками.


4

Ні, не всі функції навіть мають обертання. Наприклад, якою була б обернена функція?

f x = 1

Ваша функція є постійною, тут мова йде про біективні функції.
Soleil - Mathieu Prévot
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.