純粋な関数型言語で、逆関数を取得するアルゴリズムはありますか?


100

Haskellのような純粋な関数型言語で、全単射の場合に関数の逆を取得するアルゴリズムはありますか(編集)?そして、あなたの関数をプログラムする特定の方法はありますか?


5
数学的には、の場合f x = 1、1の逆数は整数のセットであり、それ以外の逆数は空のセットであると言っても間違いありません。いくつかの答えが何であるかに関わらず、全単射ではない機能は最大の問題ではありません。
KarolisJuodelė2012年

2
正しい応答はYESですが、効率的ではありません。f:A-> BおよびAを有限とすると、b€Bが与えられた場合、すべてのf(A)を検査して、f(a)= bであるすべてのa€Aを見つける必要があります。量子コンピューターでは、おそらくO(size(a))の複雑さになります。もちろん、実用的なアルゴリズムを探します。それは(O(2 ^ size(a))を持っています)ではありませんが、存在します...
josejuan

QuickCheckはそれを正確に実行しています(f:A-> BoolでFalseを探します)。
josejuan

4
@KarolisJuodelė:同意しません。これは通常、逆の意味ではありません。ほとんど私が用語に遭遇するたびに、の逆f関数であるgようにf . g = idg . f = id。その場合、候補者はタイプチェックすらしません。
ベンミルウッド、2012年

3
@BenMillwood、あなたは正しい。私が言ったことは、逆関数ではなく、逆画像と呼ばれます。私の要点は、f x = 1逆がないことを指摘する答えは非常に狭いアプローチをとり、問題の複雑さ全体を無視することでした。
KarolisJuodelė

回答:


101

いくつかのケースでは、はい!Bidirectionalization for Freeと呼ばれる美しい論文があります!いくつかのケースについて説明します-関数が十分に多形である場合-可能な場合、完全に自動的に逆関数を導出します。(また、関数が多態性でない場合に問題が困難になる理由についても説明します。)

関数が反転可能である場合に得られるのは、(スプリアス入力を使用した)逆です。他の場合では、古い入力値と新しい出力値を「マージ」しようとする関数を取得します。


3
以下は、双方向化における最新技術を調査した最新の論文です。「構文」およびコンビネーター
bonn.de

そして、ちょうど言及し、2008年に反転させるための邪悪なハックと-Cafeにこのメッセージあったが、put導出任意のレコード構造に機能をDatahaskell.org/pipermail/haskell-cafe/2008-April/042193.htmlはと同様のアプローチを使用してそれは後に(より厳密に、より一般的に、より原理的になど)「無料」で提示されました。
sclv

それは2017年で、もちろん紙へのリンクはもうここには有効ではありません更新1である: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≤ng 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

その時点まで、少なくとも1つはg jと区別がつかず、これらの評価のいずれも行っfinv fいなかったため、それを区別するinvことはできなかったでしょう。IO Monad

                                                                                                                                   ⬜


19

ウィキペディアで調べることができます。これはReversible Computingと呼ばれています。

一般的にはそれを行うことはできませんし、関数型言語にはそのオプションはありません。例えば:

f :: a -> Int
f _ = 1

この関数には逆はありません。


1
fが逆行列を持っていると言うのは間違っているでしょうか?それは逆行列が非決定論的な関数であるということだけですか?
Matt Fenwick、2012年

10
@MattFenwickたとえば、Haskellのような言語では、関数は単純に非決定的ではありません(型や使用方法を変更しない限り)。の逆を数学的に記述できてもg :: Int -> a、の逆であるHaskell関数は存在しません。ff
ベン

2
@マット:関数型プログラミングとロジックの「下」を検索します。「ボトム」は、「不可能な」値です。これは、矛盾する、終了しない、または決定不能な問題に対するソリューションであるためです(これは、単に矛盾するだけではなく、設計を検討しながら、体系的にソリューションを「追跡」することができます)。開発中に「未定義」と「エラー」を使用するスペース)。「ボトム」xのタイプはaです。それはあらゆるタイプの「生息」(または「価値」)です。タイプは命題であり、すべての命題を満たす値はないため、これは論理的に矛盾しています。Haskell-Cafeで良いディスカッションを探してください
nomen '23

2
@Matt:非決定性の観点から逆の非存在を特徴付けるのではなく、ボトムスの観点から特徴付ける必要があります。f _ = 1の逆は、すべての型に存在する必要があるため、底になります(または、fには、単一の要素を超える型の逆関数がないため、底になります。ボトムであることは、価値についての断言として、肯定的にも否定的にも取ることができます。任意の関数の逆を「価値」の底であると感覚的に話すことができます。(「実際の」値ではありませんが)
nomen

1
ずっと後に戻ってここをさまよったとき、私はマットが何をしているのかがわかると思います。リストを介して非決定性をモデル化することが多く、逆についても同じことができます。f x = 2 * xbe の逆数としf' x = [x / 2]、次にf _ = 1is の逆数としますf' 1 = [minBound ..]; f' _ = []。つまり、1の逆数は多く、他の値の逆数はありません。
アマロイ2015年

16

ほとんどの関数型言語ではなく、ロジックプログラミングまたはリレーショナルプログラミングでは、定義するほとんどの関数は実際には関数ではなく「リレーション」であり、これらは双方向で使用できます。たとえば、prologまたはkanrenを参照してください。


1
または水星、それ以外の場合はHaskellの精神の多くを共有します。—良い点、+ 1。
leftaroundabout

11

このようなタスクは、ほとんどの場合決定不可能です。いくつかの特定の機能に対する解決策を持つことができますが、一般的にはできません。

ここでは、どの関数が逆関数を持っているかさえ認識できません。HPラムダ計算:Barendregtの引用:その構文とセマンティクス。北ホラント、アムステルダム(1984)

ラムダ項のセットが空でも完全でもない場合、それは重要です。AとBが2つの自明ではない、ばらばらのラムダ項のセットがβ等式の下で閉じている場合、AとBは再帰的に分離不可能です。

Aを可逆関数を表すラムダ項のセットとし、残りをBとしましょう。どちらも空ではなく、ベータの平等で閉鎖されています。したがって、関数が可逆であるかどうかを判断することはできません。

(これは型なしラムダ計算に当てはまります。TBH反転したい関数の型がわかっているときに、引数が型付きラムダ計算に直接適応できるかどうかはわかりません。しかし、私はそれが間違いなく同様です。)


11

関数のドメインを列挙でき、範囲の要素が等しいかどうか比較できる場合は、かなり簡単にできます。列挙することは、利用可能なすべての要素のリストを持つことを意味します。私はOcaml(またはそれを適切に大文字にする方法さえも知らないので、私はHaskellに固執します;-)

あなたがしたいことは、ドメインの要素を実行し、それらが反転しようとしている範囲の要素と等しいかどうかを確認し、機能する最初の要素を取得することです。

inv :: Eq b => [a] -> (a -> b) -> (b -> a)
inv domain f b = head [ a | a <- domain, f a == b ]

あなたがそれをf全単射であると述べたので、そのような要素はたった1つしかありません。もちろん、コツは、ドメインの列挙が有限時間内にすべての要素実際に到達することを保証することです。あなたからの全単射を反転しようとしている場合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タイプの間の秒ab

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

私は最近このような問題に対処しており、いいえ、(a)多くの場合、難しくはありませんが、(b)まったく効率的ではないと言います。

基本的に、あなたが持っていると仮定します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 @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

BoundedEnumインスタンスを持つタイプは簡単に作成でき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

すべての関数に逆関数があるわけではありません。議論を1対1の機能に限定すると、任意の機能を反転できる機能により、暗号システムを解読する機能が付与されます。理論的にも、これが実現不可能であることを期待する必要があります。


13
任意の暗号システム(ワンタイムパッドなど、他の理由で実行できない奇妙な暗号を除く)は、ブルートフォースによってクラックされる可能性があります。それでもそれらの有用性が低くなることはありませんし、非現実的に高価な反転関数になることもありません。

ほんと?暗号化機能をString encrypt(String key, String text)キーがないと考えても、まだ何もできません。編集:デルナンの発言に加えて。
mck

@MaciekAlbinは攻撃モデルによって異なります。たとえば、選択されたプレーンテキスト攻撃では、キーを抽出することができ、そのキーで暗号化された他の暗号テキストを攻撃することができます。

「実現可能」とは、妥当な時間内に実行できることを意味しました。私は「計算可能」という意味ではありませんでした(私はかなり確信しています)。
Jeffrey Scofield

@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

あなたの関数は定数です、ここでは全単射関数についてです。
ソレイユ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.