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