Haskellのような純粋な関数型言語で、全単射の場合に関数の逆を取得するアルゴリズムはありますか(編集)?そして、あなたの関数をプログラムする特定の方法はありますか?
f
関数であるg
ようにf . g = id
とg . f = id
。その場合、候補者はタイプチェックすらしません。
f x = 1
逆がないことを指摘する答えは非常に狭いアプローチをとり、問題の複雑さ全体を無視することでした。
Haskellのような純粋な関数型言語で、全単射の場合に関数の逆を取得するアルゴリズムはありますか(編集)?そして、あなたの関数をプログラムする特定の方法はありますか?
f
関数であるg
ようにf . g = id
とg . f = id
。その場合、候補者はタイプチェックすらしません。
f x = 1
逆がないことを指摘する答えは非常に狭いアプローチをとり、問題の複雑さ全体を無視することでした。
回答:
いくつかのケースでは、はい!Bidirectionalization for Freeと呼ばれる美しい論文があります!いくつかのケースについて説明します-関数が十分に多形である場合-可能な場合、完全に自動的に逆関数を導出します。(また、関数が多態性でない場合に問題が困難になる理由についても説明します。)
関数が反転可能である場合に得られるのは、(スプリアス入力を使用した)逆です。他の場合では、古い入力値と新しい出力値を「マージ」しようとする関数を取得します。
put
導出任意のレコード構造に機能をData
:haskell.org/pipermail/haskell-cafe/2008-April/042193.htmlはと同様のアプローチを使用してそれは後に(より厳密に、より一般的に、より原理的になど)「無料」で提示されました。
いいえ、それは一般的に不可能です。
証明:タイプの全単射関数を検討する
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
その時点まで、少なくとも1つはg j
と区別がつかず、これらの評価のいずれも行っf
てinv f
いなかったため、それを区別するinv
ことはできなかったでしょう。IO Monad
。
⬜
ウィキペディアで調べることができます。これはReversible Computingと呼ばれています。
一般的にはそれを行うことはできませんし、関数型言語にはそのオプションはありません。例えば:
f :: a -> Int
f _ = 1
この関数には逆はありません。
f
が逆行列を持っていると言うのは間違っているでしょうか?それは逆行列が非決定論的な関数であるということだけですか?
g :: Int -> a
、の逆であるHaskell関数は存在しません。f
f
f x = 2 * x
be の逆数としf' x = [x / 2]
、次にf _ = 1
is の逆数としますf' 1 = [minBound ..]; f' _ = []
。つまり、1の逆数は多く、他の値の逆数はありません。
ほとんどの関数型言語ではなく、ロジックプログラミングまたはリレーショナルプログラミングでは、定義するほとんどの関数は実際には関数ではなく「リレーション」であり、これらは双方向で使用できます。たとえば、prologまたはkanrenを参照してください。
このようなタスクは、ほとんどの場合決定不可能です。いくつかの特定の機能に対する解決策を持つことができますが、一般的にはできません。
ここでは、どの関数が逆関数を持っているかさえ認識できません。HPラムダ計算:Barendregtの引用:その構文とセマンティクス。北ホラント、アムステルダム(1984):
ラムダ項のセットが空でも完全でもない場合、それは重要です。AとBが2つの自明ではない、ばらばらのラムダ項のセットがβ等式の下で閉じている場合、AとBは再帰的に分離不可能です。
Aを可逆関数を表すラムダ項のセットとし、残りをBとしましょう。どちらも空ではなく、ベータの平等で閉鎖されています。したがって、関数が可逆であるかどうかを判断することはできません。
(これは型なしラムダ計算に当てはまります。TBH反転したい関数の型がわかっているときに、引数が型付きラムダ計算に直接適応できるかどうかはわかりません。しかし、私はそれが間違いなく同様です。)
関数のドメインを列挙でき、範囲の要素が等しいかどうか比較できる場合は、かなり簡単にできます。列挙することは、利用可能なすべての要素のリストを持つことを意味します。私は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のパッケージには、良い方法でタプルエトセトラのリストを介して実行することができます。そのようなパッケージは他にもあると思いますが、それらは知りません。
もちろん、このアプローチは醜く非効率的であることは言うまでもなく、少額で総当たり的なものです!だから私はあなたの質問の最後の部分で、全単射を「書く」方法についていくつかのコメントで終わります。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
手作業で定数を記述しなければならない回数は非常に限られたものになると思います。
結局のところ、関数が全単射であることがわかっている場合は、頭にその事実の証明スケッチがあるはずです。カレーハワード同型はプログラムに変換できるはずです:-)
私は最近このような問題に対処しており、いいえ、(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
。
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
おそらくあらゆる代数データ型に対して実行できることを意味します。
すべての関数に逆関数があるわけではありません。議論を1対1の機能に限定すると、任意の機能を反転できる機能により、暗号システムを解読する機能が付与されます。理論的にも、これが実現不可能であることを期待する必要があります。
String encrypt(String key, String text)
キーがないと考えても、まだ何もできません。編集:デルナンの発言に加えて。
場合によっては、全単射関数の逆を記号表現に変換することで見つけることができます。この例に基づいて、私はいくつかの単純な多項式関数の逆を見つけるためにこの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
この例は算術式でのみ機能しますが、リストで機能するように一般化することもできます。
f x = 1
、1の逆数は整数のセットであり、それ以外の逆数は空のセットであると言っても間違いありません。いくつかの答えが何であるかに関わらず、全単射ではない機能は最大の問題ではありません。