Saf işlevsel dillerde, ters işlevi elde etmek için bir algoritma var mı?


100

Haskell gibi saf işlevsel dillerde, bir işlevin tersini elde etmek için bir algoritma var mı? Ve işlevinizi öyle programlamanın belirli bir yolu var mı?


5
Matematiksel olarak, f x = 11'in tersinin bir tamsayılar kümesi olduğunu ve başka herhangi bir şeyin tersinin boş bir küme olduğunu söylemek yanlış değildir . Bazı yanıtların ne dediğine bakılmaksızın, işlevin önyargılı olmaması en büyük sorun değildir.
Karolis Juodelė

2
Doğru yanıt EVET'tir, ancak verimli değildir. F: A -> B ve A sonlu olsun, o zaman, b € B verildiğinde, "sadece" f (a) = b olan tüm a € A'yı bulmak için tüm f (A) 'yı incelemelisiniz. Bir kuantum bilgisayarda, belki de O (boyut (a)) karmaşıklığı olabilir. Elbette pratik bir algoritma arıyorsunuz. Değil (O (2 ^ size (a))), ama var ...
josejuan

QuickCheck bunu tam olarak yapıyor (f: A -> Bool'da bir False ararlar).
josejuan

4
@ KarolisJuodelė: Katılmıyorum; ters ile kastedilen genellikle bu değildir. Hemen hemen ben terimle karşılaşırsanız her zaman, ters fbir fonksiyonudur gşekilde f . g = idve g . f = id. Adayınız bu durumda kontrol bile yazmıyor.
Ben Millwood

3
@BenMillwood, haklısın. Söylediğim şeye ters bir işlev değil, ters görüntü denir. Demek istediğim f x = 1, tersi olmadığını gösteren cevapların çok dar bir yaklaşım benimsemesi ve sorunun tüm karmaşıklığını görmezden gelmesiydi.
Karolis Juodelė

Yanıtlar:


101

Bazı durumlarda evet! Ücretsiz Bidirectionalization adında güzel bir kağıt var ! Bu, birkaç durumu tartışır - fonksiyonunuz yeterince polimorfik olduğunda - ters fonksiyon türetmenin tamamen otomatik olarak mümkün olduğu durumlarda. (Ayrıca, işlevler polimorfik olmadığında sorunu neyin zorlaştırdığını da tartışır.)

Fonksiyonunuzun tersine çevrilebilir olması durumunda ortaya çıkardığınız şey tersidir (sahte bir girişle); diğer durumlarda, eski bir girdi değerini ve yeni bir çıktı değerini "birleştirmeye" çalışan bir işlev alırsınız.


3
İşte çift yönlüleşmedeki en son gelişmeleri inceleyen daha yeni bir makale. "Sözdizimsel" ve birleştirici tabanlı yaklaşımlar da dahil olmak üzere üç teknik ailesi içerir: iai.uni-bonn.de/~jv/ssgip-bidirectional-final.pdf
sclv

Ve sadece söz etmek, 2008 yılında inversini kötü bir hack ile -cafe bu mesajı vardı putkaynaklanan herhangi rekor yapılarına fonksiyonları Data: haskell.org/pipermail/haskell-cafe/2008-April/042193.html benzer bir yaklaşım kullanarak daha sonra (daha kesin, daha genel, daha ilkeli, vb.) "ücretsiz" olarak sunuldu.
sclv


37

Hayır, genel olarak mümkün değil.

İspat: tipin önyargılı işlevlerini düşünün

type F = [Bit] -> [Bit]

ile

data Bit = B0 | B1

inv :: F -> FBöyle bir invertörümüz olduğunu varsayalım inv f . f ≡ id. Diyelim ki işlev için test ettiğimizi f = idonaylayarak

inv f (repeat B0) -> (B0 : ls)

B0Çıktıdaki bu ilk bir sonlu süreden sonra gelmiş olması gerektiğinden n, hem invbu sonucu elde etmek için test girdimizi gerçekten değerlendiren derinlikte hem de kaç kez çağrılabileceği konusunda bir üst sınırımız var f. Şimdi bir işlev ailesi tanımlayın

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

Açıkçası, herkes için 0<j≤n, g jaslında kendi kendine ters olan bir eşleştirme. Yani onaylayabilmeliyiz

inv (g j) (replicate (n+j) B0 ++ B1 : repeat B0) -> (B1 : ls)

ama bunu gerçekleştirmek inv (g j)için ya

  • g j (B1 : repeat B0)derinliğine değerlendirmekn+j > n
  • eşleşen head $ g j len az nfarklı listeler için değerlendirreplicate (n+j) B0 ++ B1 : ls

Bu noktaya kadar, bunlardan en az biri g jayırt edilemezdi fve inv fbu değerlendirmelerden hiçbirini yapmadığı inviçin, onu ayırması mümkün değildi - bazı çalışma zamanı ölçümlerini kendi başına yapmak dışında, ki bu sadece IO Monad.

                                                                                                                                   ⬜


19

Wikipedia'dan bakabilirsiniz, adı Tersine Çevrilebilir Hesaplama .

Genel olarak bunu yapamazsınız ve işlevsel dillerin hiçbirinde bu seçenek yoktur. Örneğin:

f :: a -> Int
f _ = 1

Bu işlevin tersi yoktur.


1
Bunun ftersi olduğunu söylemek yanlış olur , sadece tersi deterministik olmayan bir fonksiyondur?
Matt Fenwick

10
@MattFenwick Örneğin Haskell gibi bir dilde, işlevler kesin olarak kesin değildir (türleri ve onları kullanma şeklinizi değiştirmeden). Matematiksel olarak tersini tanımlayabilseniz bile g :: Int -> a, tersi olan herhangi bir Haskell işlevi yoktur . ff
Ben

2
@Matt: Fonksiyonel programlama ve mantıkta "aşağıya" bakın. Bir "dip" "imkansız" bir değerdir, çünkü çelişkili olduğu, sonlanmadığı ya da karar verilemeyen bir sorunun çözümü olduğu için (bu sadece çelişkili olmaktan öte - bir tasarımı keşfederken metodik olarak bir çözümü "kovalayabiliriz") geliştirme sırasında "tanımsız" ve "hata" kullanan alan). Bir "alt" x, a türüne sahiptir. Her türden "yaşar" (veya "değer" dir). Bu mantıksal bir çelişkidir, çünkü türler önermelerdir ve her önermeyi karşılayan hiçbir değer yoktur. İyi tartışmalar için Haskell-Cafe'ye bakın
nomen

2
@Matt: Terslerin varolmayışını determinizm olmama açısından nitelendirmek yerine, onu dipler açısından karakterize etmek gerekir. F _ = 1'in tersi alttadır, çünkü her türde yaşaması gerekir (alternatif olarak, f'nin tek bir öğeden daha fazlasına sahip herhangi bir tür için ters işlevi olmadığından alttır - odaklandığınız yön, sanırım). Dip olmak, değerler hakkındaki iddialar olarak hem olumlu hem de olumsuz olarak alınabilir. Biri, keyfi bir fonksiyonun tersinin "değer" tabanı olarak mantıklı bir şekilde söz edilebilir. ("Gerçekten" bir değer olmasa da)
nomen

1
Buraya çok daha sonra geri döndükten sonra, sanırım Matt'in neye vardığını anlıyorum: genellikle listelerle kesinleşmeme modelini modelliyoruz ve aynısını tersler için de yapabiliriz. Tersini yapalım f x = 2 * xyönelik olacaktır f' x = [x / 2]ve sonra tersini f _ = 1DİR f' 1 = [minBound ..]; f' _ = []. Yani, 1 için birçok ters vardır ve başka herhangi bir değer için hiçbiri yoktur.
amalloy

16

İşlevsel dillerin çoğunda değil, mantık programlamasında veya ilişkisel programlamada, tanımladığınız işlevlerin çoğu aslında işlevler değil "ilişkilerdir" ve bunlar her iki yönde de kullanılabilir. Örneğin prolog veya kanren'e bakın.


1
Veya başka türlü Haskell'in ruhunun çoğunu paylaşan Merkür . - İyi nokta, +1.
leftaroundabout

11

Bunun gibi görevler neredeyse her zaman kararlaştırılamaz. Bazı özel işlevler için bir çözümünüz olabilir, ancak genel olarak değil.

Burada hangi fonksiyonların tersi olduğunu bile anlayamazsınız. Barendregt'ten alıntı , HP Lambda Hesabı: Sözdizimi ve Anlambilim. Kuzey Hollanda, Amsterdam (1984) :

Ne boş ne de dolu küme ise, bir dizi lambda-terim önemsizdir. A ve B, (beta) eşitliği altında kapalı iki önemsiz, ayrık lambda terimleri kümesiyse, o zaman A ve B özyinelemeli olarak birbirinden ayrılamaz.

A'yı tersinir fonksiyonları temsil eden lambda terimleri kümesi olarak ve B'yi geri alalım. Her ikisi de boş değil ve beta eşitliği altında kapalı. Dolayısıyla bir fonksiyonun tersinir olup olmadığına karar vermek mümkün değildir.

(Bu türlenmemiş lambda hesabı için geçerlidir. TBH Tersine çevirmek istediğimiz bir işlevin türünü bildiğimizde, argümanın doğrudan yazılı bir lambda hesabına uyarlanıp uyarlanamayacağını bilmiyorum. Ama bunun olacağından oldukça eminim benzer.)


11

İşlevin etki alanını numaralandırabilir ve eşitlik aralığının öğelerini karşılaştırabilirseniz, bunu oldukça basit bir şekilde yapabilirsiniz. Numaralandırmakla, mevcut tüm öğelerin bir listesine sahip olmayı kastediyorum. Ocaml'ı bilmediğim için Haskell'e bağlı kalacağım (ya da nasıl büyük harfle yazılacağını bile ;-)

Yapmak istediğiniz şey, etki alanının öğelerini incelemek ve ters çevirmeye çalıştığınız aralığın öğesine eşit olup olmadıklarına bakmak ve işe yarayan ilkini almaktır:

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

Bunun fbir bijeksiyon olduğunu belirttiğinize göre , böyle bir unsur olması kaçınılmazdır. İşin püf noktası, elbette, etki alanı numaralandırmanızın aslında tüm öğelere sonlu bir zamanda ulaşmasını sağlamaktır . Eğer bir eşleşme ters çalışıyorsanız Integeriçin Integerkullanırken, [0,1 ..] ++ [-1,-2 ..]olmaz işi negatif sayılar olsun asla olarak. Somut olarak, inv ([0,1 ..] ++ [-1,-2 ..]) (+1) (-3)asla bir değer vermeyecektir.

Ancak, 0 : concatMap (\x -> [x,-x]) [1..]bu tamsayılar aşağıdaki sırayla çalıştığı için çalışacaktır [0,1,-1,2,-2,3,-3, and so on]. Indeed inv (0 : concatMap (\x -> [x,-x]) [1..]) (+1) (-3)hemen geri dönüyor -4!

Control.Monad.Omega paketi iyi bir şekilde küpe vs. listeleri yoluyla yönetmesini sağlayacak; Eminim bunun gibi daha fazla paket vardır - ama onları bilmiyorum.


Tabii ki, bu yaklaşım oldukça alçakgönüllü ve kaba kuvvetlidir, çirkin ve verimsizden bahsetmeye bile gerek yok! Öyleyse, sorunuzun son bölümünde, önyargıları nasıl 'yazacağınıza' dair birkaç yorumla bitireceğim. Haskell'in tip sistemi, bir fonksiyonun bir bijeksiyon olduğunu kanıtlamakla kalmaz - bunun için gerçekten Agda gibi bir şey istersiniz - ama size güvenmeye isteklidir.

(Uyarı: test edilmemiş kod izler)

Yani, Bijectiontürler arasında bir veri türü tanımlayabilir ave b:

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

İstediğiniz kadar sabitle birlikte (burada ' Önyükleme olduklarını biliyorum !' diyebilirsiniz !), örneğin:

notBi :: Bi Bool Bool
notBi = Bi not not

add1Bi :: Bi Integer Integer
add1Bi = Bi (+1) (subtract 1)

ve birkaç akıllı birleştirici, örneğin:

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)

Bence o zaman yapabilir invert (mapBi add1Bi) [1,5,6]ve alabilirsin [0,4,5]. Kombinatörlerinizi akıllıca seçerseniz Bi, elle sabit yazmak zorunda kalacağınız zamanın sayısı oldukça sınırlı olabilir.

Sonuçta, eğer bir fonksiyonun bijeksiyon olduğunu biliyorsanız, umarım bu gerçeğin kafanızda Curry-Howard izomorfizminin bir programa dönüştürebilmesi gereken bir kanıt taslağı olur :-)


6

Son zamanlarda bunun gibi konularla uğraşıyorum ve hayır, şunu söyleyebilirim (a) çoğu durumda zor değil, ama (b) hiç verimli değil.

Temel olarak, varsayalım ki sende var f :: a -> bve bu fgerçekten bir bjiection. Tersini f' :: b -> agerçekten aptalca bir şekilde hesaplayabilirsiniz :

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

Eğer fbir bijection ise ve enumerategerçekten tüm değerleri üretiyorsa a, sonunda aöyle bir şeye ulaşırsınız f a == b.

A Boundedve bir Enumörneğe sahip türler önemsiz bir şekilde yapılabilir RecursivelyEnumerable. Tür çiftleri Enumerablede yapılabilir 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

Aynısı Enumerabletürlerin ayrılması için de geçerlidir :

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

Aslında biz de bu yapabileceği (,)ve Eitherherhangi cebirsel veri türü için yapabilir muhtemelen anlamına gelir.


5

Her işlevin tersi yoktur. Tartışmayı bire bir işlevlerle sınırlarsanız, rastgele bir işlevi tersine çevirme yeteneği, herhangi bir şifreleme sistemini kırma yeteneği sağlar. Bunun teoride bile mümkün olmadığını ummalıyız!


13
Herhangi bir şifreleme sistemi (başka nedenlerle mümkün olmayan bir zaman pedleri gibi birkaç garip olanlar hariç) kaba kuvvetle kırılabilir. Bu onları daha az kullanışlı yapmaz ve pratik olarak pahalı bir ters çevirme işlevi de olmaz.

Gerçekten öyle mi? Şifreleme işlevini String encrypt(String key, String text)anahtarsız olarak düşünürseniz, yine de hiçbir şey yapamazsınız. DÜZENLEME: Ayrıca delnan'ın söylediği.
mck

@MaciekAlbin Saldırı modelinize bağlıdır. Örneğin, seçili düz metin saldırıları, anahtarın çıkarılmasına izin verebilir ve bu daha sonra bu anahtarla şifrelenmiş diğer şifre metinlerine saldırılmasına izin verir.

"Uygulanabilir" derken, makul bir sürede yapılabilecek bir şeyi kastettim. "Hesaplanabilir" demek istemedim (oldukça eminim).
Jeffrey Scofield

@JeffreyScofield Ne demek istediğini anlıyorum. Ama şunu söylemeliyim ki, "teoride uygulanabilir" ile kafam karıştı - fizibilite (bizim tanımımız) sadece pratikte yapmanın ne kadar zor olduğunu belirtmiyor mu?

5

Bazı durumlarda, bir önyargı işlevinin tersini sembolik bir temsile dönüştürerek bulmak mümkündür. Bu örneğe dayanarak, bazı basit polinom fonksiyonlarının tersini bulmak için bu Haskell programını yazdım:

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

Bu örnek yalnızca aritmetik ifadelerle çalışır, ancak muhtemelen listelerle de çalışmak üzere genelleştirilebilir.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.