Düzeltmeyi nasıl kullanırım ve nasıl çalışır?


88

Belgeler beni biraz karıştırdı fix(şimdi ne yapması gerektiğini anladığımı düşünmeme rağmen), bu yüzden kaynak koda baktım. Bu beni daha çok karıştırdı:

fix :: (a -> a) -> a
fix f = let x = f x in x

Bu tam olarak nasıl sabit bir noktayı döndürür?

Komut satırında denemeye karar verdim:

Prelude Data.Function> fix id
...

Ve orada asılı duruyor. Şimdi adil olmak gerekirse, bu biraz yavaş olan eski macbook'umda. Bununla birlikte, bu işlev, id'ye aktarılan herhangi bir şey aynı şeyi geri verdiğinden hesaplama açısından çok pahalı olamaz (CPU zamanını tüketmediğinden bahsetmiyorum bile). Neyi yanlış yapıyorum?


69
Şakanın cevabı "düzeltmenin gerçek bir faydası yoktur, sadece oradadır, böylece fix errorghci yazabilir ve kendiniz hakkında iyi hissedebilirsiniz."
Thomas M. DuBuisson

3
@TomMD - Komik. Biri bana neyin düzeltildiğini sorarsa ve kendimi huysuz hissettiğimi hatırlayacağım. :-)
Jason Baker

2
Genellikle yazma fixolarak fix f = f (fix f). Kısa, basit, işe yarıyor ve matematiksel tanımla aynı.
newacct

20
@newacct, evet ben de öyle düşünüyorum. Ancak buradaki daha verimli yapılara yol açabilir. Diyelim ki değerlendirirseniz farkı görebilirsiniz fix (1:) !! (10^8). Orijinal bunu sabit bellekte yapar, sizinki doğrusal belleği alır (bu da onu biraz daha yavaş yapar). Yani, let'i kullanmak "daha sıkı bir düğüm atar" ve dairesel bir veri yapısının oluşturulmasına izin verirken sizinki oluşturmaz.
luqui

22
Sen de yeniden icat edebilirdin fix! anlamama fixçok yardımcı oldu .
fredoverflow

Yanıtlar:


91

Yanlış bir şey yapmıyorsun. fix idsonsuz bir döngüdür.

Bunun fixbir fonksiyonun en az sabit noktasını döndürdüğünü söylediğimizde , alan teorisi anlamında bunu kastediyoruz . Yani fix (\x -> 2*x-1)bir değil dönecek 1olsa da, baz 1bu işlevin sabit noktasıdır, değil mi az etki sıralamada tek.

Alan sıralamasını sadece bir veya iki paragrafta tarif edemem, bu yüzden sizi yukarıdaki alan teorisi bağlantısına yönlendireceğim. Mükemmel bir öğretici, okunması kolay ve oldukça aydınlatıcı. Şiddetle tavsiye ederim.

10.000 fitten görünüm fixiçin, özyineleme fikrini kodlayan bir üst düzey fonksiyondur . İfadeye sahipseniz:

let x = 1:x in x

Sonsuz listeyle sonuçlanan [1,1..], aynı şeyi şunu kullanarak söyleyebilirsin fix:

fix (\x -> 1:x)

(Ya da basitçe fix (1:)), bana (1:)fonksiyonun sabit bir noktasını bul diyor , IOW a değer xöyle ki x = 1:x... aynı yukarıda tanımladığımız gibi. Tanımdan da görebileceğiniz gibi, fixbu fikirden başka bir şey değildir - özyineleme bir işleve çevrilmiştir.

Bu aynı zamanda gerçekten genel bir özyineleme kavramıdır - polimorfik özyinelemeyi kullanan işlevler de dahil olmak üzere herhangi bir özyinelemeli işlevi bu şekilde yazabilirsiniz . Örneğin, tipik fibonacci işlevi:

fib n = if n < 2 then n else fib (n-1) + fib (n-2)

Bu şekilde yazılabilir fix:

fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))

Alıştırma: fixBu iki tanımın fibeşdeğer olduğunu göstermek için tanımını genişletin .

Ancak tam bir anlayış için, alan teorisi hakkında okuyun. Gerçekten harika şeyler.


32
İşte düşünmenin ilgili bir yolu fix id: fixtürden bir işlevi alır a -> ave bir tür değeri döndürür a. Çünkü idherhangi biri için polimorfik olan a, fix idtip olacak a, herhangi bir olası değeri yani. Haskell'de, herhangi bir türden olabilen tek değer diptir, ve sonlandırıcı olmayan bir hesaplamadan ayırt edilemez. Yani fix idtam olarak olması gerekeni üretir, alt değeri. Bir tehlike, fixeğer ⊥ fonksiyonunuzun sabit bir noktasıysa, o zaman tanım gereği en az sabit nokta olduğundan, bu nedenle fixsona ermeyecektir.
John L

4
Haskell'deki @JohnL undefinedde her türden bir değerdir. Sen tanımlayabilirsiniz fixolarak: fix f = foldr (\_ -> f) undefined (repeat undefined).
didest

1
@Diego kodunuzun eşdeğeridir _Y f = f (_Y f).
Will Ness

25

Bunu hiç anladığımı iddia etmiyorum, ama eğer bu birine yardım ederse ... o zaman yippee.

Tanımını düşünün fix. fix f = let x = f x in x. Akıllara durgunluk veren kısım, şu xşekilde tanımlanmaktadır f x. Ama bir dakika düşünün.

x = f x

X = fx xolduğuna göre, bunun sağ tarafındaki değerini değiştirebiliriz , değil mi? Yani bu nedenle...

x = f . f $ x -- or x = f (f x)
x = f . f . f $ x -- or x = f (f (f x))
x = f . f . f . f . f . f . f . f . f . f . f $ x -- etc.

Öyleyse işin püf noktası, sona erdirmek için, fbir tür yapı oluşturmak zorundadır, böylece daha sonra fbu yapıyı eşleştirebilir ve parametresinin (?) Tam "değerini" umursamadan yinelemeyi sonlandırabilir

Tabii ki luqui'nin gösterdiği gibi sonsuz bir liste oluşturmak gibi bir şey yapmak istemiyorsan.

TomMD'nin faktöriyel açıklaması iyidir. Düzeltmenin tür imzası (a -> a) -> a. Tip imzası (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)olan (b -> b) -> b -> b, başka bir deyişle, (b -> b) -> (b -> b). Yani bunu söyleyebiliriz a = (b -> b). Bu şekilde, bir düzeltmedir bizim fonksiyonunu alır a -> a, ya da gerçekten (b -> b) -> (b -> b)ve türünde bir sonuç dönecek a, başka bir deyişle, b -> bbaşka bir deyişle, başka bir işlev!

Bekle, sabit bir noktaya dönmesi gerektiğini sanıyordum ... bir fonksiyon değil. Evet, öyle (çünkü fonksiyonlar veri). Bize bir faktöriyel bulma konusunda kesin bir işlev verdiğini hayal edebilirsiniz. Ona nasıl tekrarlanacağını bilmeyen bir fonksiyon verdik (bu nedenle parametrelerinden biri tekrarlamak için kullanılan bir fonksiyondur) ve fixona nasıl tekrarlanacağını öğrettik.

Bunun bir ftür yapı oluşturması gerektiğini söylediğimi hatırlıyor fmusunuz, böylece daha sonra örüntü eşleşip sonlanabilir mi? Bu tam olarak doğru değil sanırım. TomMD x, işlevi uygulamak ve temel duruma doğru adım atmak için nasıl genişleyebileceğimizi gösterdi. İşlevi için, eğer / o zaman kullandı ve feshin nedeni budur. Tekrarlanan değiştirmelerden sonra in, tüm tanımın bir kısmı fixsonunda terimleriyle tanımlanmayı durdurur xve bu, hesaplanabilir ve tamamlandığında gerçekleşir.


Teşekkürler. Bu çok kullanışlı ve pratik bir açıklamadır.
kizzx2

18

Sabit noktayı sonlandırmak için bir yola ihtiyacınız var. Örneğinizi genişletirseniz, bitmeyeceği açıktır:

fix id
--> let x = id x in x
--> id x
--> id (id x)
--> id (id (id x))
--> ...

İşte düzeltme kullanımımla ilgili gerçek bir örnek (düzeltmeyi çok sık kullanmadığımı ve muhtemelen yorulmuş olduğumu / bunu yazdığımda okunabilir kod konusunda endişelenmediğimi unutmayın):

(fix (\f h -> if (pred h) then f (mutate h) else h)) q

WTF diyorsun! Evet, ama burada gerçekten yararlı birkaç nokta var. Her şeyden önce, ilk fixargümanınız genellikle 'tekrarlama' durumu olan bir işlev olmalıdır ve ikinci argüman, üzerinde hareket edilecek verilerdir. İşte adlandırılmış bir işlevle aynı kod:

getQ h
      | pred h = getQ (mutate h)
      | otherwise = h

Hâlâ kafanız karıştıysa, belki de faktöryel daha kolay bir örnek olacaktır:

fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 5 -->* 120

Değerlendirmeye dikkat edin:

fix (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) 3 -->
let x = (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x in x 3 -->
let x = ... in (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 3 -->
let x = ... in (\d -> if d > 0 then d * (x (d-1)) else 1) 3

Oh, bunu gördün mü? Bu x, thenşubemizin içinde bir işlev haline geldi .

let x = ... in if 3 > 0 then 3 * (x (3 - 1)) else 1) -->
let x = ... in 3 * x 2 -->
let x = ... in 3 * (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1) x 2 -->

Yukarıdakileri hatırlamanız gerekir x = f x, bu nedenle x 2sadece yerine sondaki iki argüman 2.

let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->

Ve burada duracağım!


Cevabınız aslında fixbana mantıklı gelen şey. Cevabım büyük ölçüde daha önce söylediklerinize bağlı.
Dan Burton

@Thomas hem azaltma dizileriniz yanlış. :) id xsadece küçültülür x(sonra tekrar küçülür id x). - Daha sonra 2. örnekte ( fact), xthunk ilk zorlandığında, elde edilen değer hatırlanır ve yeniden kullanılır. Yeniden hesaplanması (\recurse ...) xda olacağı olmayan paylaşım tanımına y g = g (y g)değil bununla, paylaşımfix tanımı. - Burada deneme düzenlemesini yaptım - kullanabilirsiniz, ya da onaylarsanız düzenlemeyi yapabilirim.
Will Ness

aslında, fix idazaltıldığında, çerçeve içindeki let x = id x in xuygulama değerini de zorlar (thunk), böylece azalır ve bu döngüler. Öyle görünüyor. id xletlet x = x in x
Will Ness

Doğru. Cevabım eşitlik mantığını kullanmak. Değerlendirme sırasını ilgilendiren bir Haskell indirgemesinin gösterilmesi, yalnızca gerçek bir kazanç olmaksızın örneği karıştırmaya hizmet eder.
Thomas M. DuBuisson

1
Soru hem haskell hem de letrec ile etiketlenmiştir (yani paylaşımlı özyinelemeli izin). Arasındaki fark fixve Y Haskell çok açık ve önemli bir. Doğru olan daha kısa, çok daha net ve takip edilmesi daha kolayken yanlış indirgeme sırasını göstererek neyin iyi sunulduğunu ve gerçekte neler olup bittiğini doğru şekilde yansıttığını anlamıyorum.
Will Ness

3

Ne olduğunu anladığım kadarıyla, fonksiyon için bir değer bulur, öyle ki ona verdiğiniz şeyin aynısını çıkarır. İşin özü, her zaman tanımsız (veya haskell'de sonsuz bir döngü, tanımsız ve sonsuz döngüler aynıdır) veya içinde en tanımsız olanı seçecektir. Örneğin, id ile,

λ <*Main Data.Function>: id undefined
*** Exception: Prelude.undefined

Gördüğünüz gibi, tanımsız sabit bir nokta, bu yüzden fixonu seçeceğiz. Bunun yerine (\ x-> 1: x) yaparsanız.

λ <*Main Data.Function>: undefined
*** Exception: Prelude.undefined
λ <*Main Data.Function>: (\x->1:x) undefined
[1*** Exception: Prelude.undefined

Yani fixtanımsız seçemezsiniz. Onu sonsuz döngülerle biraz daha bağlantılı hale getirmek için.

λ <*Main Data.Function>: let y=y in y
^CInterrupted.
λ <*Main Data.Function>: (\x->1:x) (let y=y in y)
[1^CInterrupted.

Yine, küçük bir fark. Peki sabit nokta nedir? Deneyelim repeat 1.

λ <*Main Data.Function>: repeat 1
[1,1,1,1,1,1, and so on
λ <*Main Data.Function>: (\x->1:x) $ repeat 1
[1,1,1,1,1,1, and so on

Bu aynı! Bu tek sabit nokta olduğu için fix, bunun üzerinde durulması gerekir. Üzgünüz fix, sizin için sonsuz döngü veya tanımsız yok.

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.