web-dev-qa-db-ja.com

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

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

91
MaiaVictor

いいえ、一般的には不可能です。

証明:型の全単射関数を考慮する

_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_の深さまで評価する
  • 少なくともn _replicate (n+j) B0 ++ B1 : lsに一致する異なるリストの_head $ g j l_を評価します

その時点まで、少なくとも1つの_g j_はfと区別できず、_inv f_はこれらの評価のいずれも実行していなかったため、invはおそらく独自にいくつかのランタイム測定を行うことを除いて、それをバラバラに言いました。これは_IO Monad_でのみ可能です。

31
leftaroundabout

ウィキペディアで調べることができます。これは リバーシブルコンピューティング と呼ばれます。

一般にそれはできませんが、どの機能言語にもそのオプションはありません。例えば:

f :: a -> Int
f _ = 1

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

16
mck

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

13
amalloy

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

やりたいことは、ドメインの要素を調べて、それらが反転しようとしている範囲の要素と等しいかどうかを確認し、動作する最初の要素を取得することです:

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

fは全単射であると述べたので、そのような要素は1つに限定されます。もちろん、コツは、ドメインの列挙がすべての要素に有限時間で実際に到達するようにすることです。 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 パッケージを使用すると、タプルなどのリストを適切に実行できます。そのようなパッケージは他にもあると思いますが、知りません。


もちろん、このアプローチはratherくて非能率的なことは言うまでもなく、かなり控えめで総当たり的なものです!それでは、あなたの質問の最後の部分である、全単射を「書く」方法についてのいくつかの発言で終わります。 Haskellの型システムは、関数が全単射であることを証明するものではありません-あなたは本当にそのためにAgdaのようなものが欲しいのですが-それはあなたを信頼してくれます。

(警告:テストされていないコードが続きます)

タイプBijectionaの間でb sのデータ型を定義できますか:

_data Bi a b = Bi {
    apply :: a -> b,
    invert :: b -> a 
}
_

好きなだけ多くの定数(「Iknowそれらは全単射だ!」)

_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定数を手動で記述する必要がある回数はかなり制限されると思います。

結局、関数が全単射であることを知っているなら、その事実を頭の中に証明するスケッチがあることを願っています。それはカリー-ハワード同型がプログラムに変換できるはずです:-)

9
yatima2975

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

ここでは、どの関数に逆関数があるのか​​さえ認識できません。引用 Barendregt、H. P. The Lambda Calculus:its Syntax and Semantics。North Holland、Amsterdam(1984)

空のセットでも完全なセットでもない場合、ラムダ項のセットは重要です。 AとBが(ベータ)等式で閉じられた2つの非自明な、互いに素なラムダ項のセットである場合、AとBは再帰的に不可分です。

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

(これは、型付けされていないラムダ計算に適用されます。TBH反転したい関数の型がわかっているときに、引数を型付きラムダ計算に直接適応できるかどうかはわかりません。確かに似ています。)

8
Petr Pudlák

私は最近このような問題に対処してきましたが、いや、(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が全単射で、enumerateaのすべての値を本当に生成する場合、最終的にf a == bなどのaにヒットします。

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の両方でこれを行うことができるという事実は、おそらく、任意の代数データ型でできることを意味します。

4
Luis Casillas

すべての関数に逆関数があるわけではありません。ディスカッションを1対1の機能に限定すると、任意の機能を反転できるため、暗号システムを解読することができます。理論的にも、これが実現不可能であることを願わなければなりません!

3

場合によっては、全単射関数の逆関数をシンボリック表現に変換することで見つけることができます。 この例 に基づいて、このHaskellプログラムを作成して、いくつかの単純な多項式関数の逆関数を見つけました。

bijective_function x = x*2+1

main = do
    print $ bijective_function 3
    print $ inverse_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

inverse X = X
inverse (Const a) = Const a
inverse (Plus (Const a) b) = (Subtract (inverse b) (Const a))
inverse (Plus b (Const a)) = inverse (Plus (Const a) b)
inverse (Mult (Const a) b) = (Div (inverse b) (Const a))
inverse (Mult b (Const a)) = inverse (Mult (Const a) b)
inverse (Negate a) = Negate $ inverse a
inverse (Asin x) = Sin $ inverse x
inverse (Acos x) = Cos $ inverse x
inverse (Atan x) = Tan $ inverse x
inverse_function x = with_function inverse x

この例は算術式でのみ機能しますが、おそらくリストでも機能するように一般化できます。

1
Anderson Green