Bagaimana cara saya menggunakan perbaikan, dan bagaimana cara kerjanya?

88

Saya agak bingung dengan dokumentasinya fix(meskipun saya pikir saya mengerti apa yang seharusnya dilakukan sekarang), jadi saya melihat kode sumbernya. Itu membuat saya lebih bingung:

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

Bagaimana tepatnya ini mengembalikan titik tetap?

Saya memutuskan untuk mencobanya di baris perintah:

Prelude Data.Function> fix id
...

Dan itu tergantung di sana. Agar adil, ini ada di macbook lama saya yang agak lambat. Namun, fungsi ini tidak bisa terlalu mahal secara komputasi karena apa pun yang diteruskan ke id mengembalikan hal yang sama (belum lagi itu tidak memakan waktu CPU). Apa yang saya lakukan salah?

Jason Baker
sumber
69
Jawaban leluconnya adalah "perbaikan tidak benar-benar berguna, itu hanya ada di sana sehingga Anda dapat mengetik fix errorghci dan merasa nyaman dengan diri Anda sendiri."
Thomas M. DuBuisson
3
@TomD - Lucu. Saya akan ingat bahwa jika ada yang bertanya kepada saya apa perbaikannya dan saya merasa kesal. :-)
Jason Baker
2
Saya biasanya menulis fixsebagai fix f = f (fix f). Singkat, sederhana, berhasil, dan identik dengan definisi matematika.
newacct
20
@newacct, ya itu cara saya memikirkannya juga. Tetapi yang di sini dapat menghasilkan struktur yang lebih efisien. Anda dapat melihat perbedaannya jika Anda mengevaluasi, misalnya fix (1:) !! (10^8),. Yang asli melakukannya dalam memori konstan, milik Anda membutuhkan memori linier (yang membuatnya sedikit lebih lambat juga). Artinya, menggunakan let "ties a tighter knot", dan memungkinkan struktur data melingkar dibuat, sedangkan milik Anda tidak.
luqui
22
Anda juga bisa menemukan kembali fix! membantu saya memahami fixbanyak hal.
fredoverflow

Jawaban:

91

Anda tidak melakukan kesalahan apa pun. fix idadalah lingkaran tak terbatas.

Ketika kami mengatakan bahwa fixmengembalikan titik tetap terkecil dari suatu fungsi, yang kami maksudkan dalam pengertian teori domain . Jadi fix (\x -> 2*x-1)ini tidak akan kembali 1, karena meskipun 1merupakan titik tetap fungsi itu, itu bukan sedikit satu di domain pemesanan.

Saya tidak dapat mendeskripsikan pemesanan domain hanya dalam satu atau dua paragraf, jadi saya akan merujuk Anda ke tautan teori domain di atas. Ini adalah tutorial yang sangat bagus, mudah dibaca, dan cukup mencerahkan. Saya sangat merekomendasikannya.

Untuk pemandangan dari 10.000 kaki, fixadalah fungsi tingkat tinggi yang mengkodekan gagasan rekursi . Jika Anda memiliki ekspresi:

let x = 1:x in x

Yang menghasilkan daftar tak terbatas [1,1..], Anda bisa mengatakan hal yang sama menggunakan fix:

fix (\x -> 1:x)

(Atau sederhananya fix (1:)), yang mengatakan carikan saya titik tetap dari (1:)fungsi, IOW nilai xsedemikian rupa sehingga x = 1:x... seperti yang kami definisikan di atas. Seperti yang Anda lihat dari definisi, fixtidak lebih dari ide ini - rekursi diringkas menjadi sebuah fungsi.

Ini juga merupakan konsep rekursi yang benar-benar umum - Anda dapat menulis fungsi rekursif apa pun dengan cara ini, termasuk fungsi yang menggunakan rekursi polimorfik . Jadi misalnya fungsi fibonacci yang khas:

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

Dapat ditulis dengan fixcara ini:

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

Latihan: perluas definisi fixuntuk menunjukkan bahwa kedua definisi fibini setara.

Tetapi untuk pemahaman penuh, baca tentang teori domain. Ini hal yang sangat keren.

luqui
sumber
32
Berikut cara terkait untuk memikirkan fix id: fixmengambil fungsi tipe a -> adan mengembalikan nilai tipe a. Karena idpolimorfik untuk apapun a, fix idakan memiliki tipe a, yaitu nilai yang mungkin. Di Haskell, satu-satunya nilai yang dapat berupa tipe apa pun adalah bottom, ⊥, dan tidak dapat dibedakan dari komputasi non-terminating. Jadi fix idmenghasilkan persis apa yang seharusnya, nilai bawah. Bahayanya fixadalah jika ⊥ adalah titik tetap dari fungsi Anda, maka menurut definisi titik tetap terkecil , oleh karena itu fixtidak akan berhenti.
John L
4
@JohnL di Haskell undefinedjuga merupakan nilai jenis apa pun. Anda dapat menentukan fixsebagai: fix f = foldr (\_ -> f) undefined (repeat undefined).
melakukan
1
@Diego sama dengan kode Anda _Y f = f (_Y f).
Will Ness
25

Saya tidak mengklaim memahami ini sama sekali, tetapi jika ini membantu siapa pun ... maka hura.

Pertimbangkan definisi fix. fix f = let x = f x in x. Bagian yang membingungkan adalah yang xdidefinisikan sebagai f x. Tapi pikirkan sebentar.

x = f x

Karena x = fx, maka kita bisa mengganti nilai xdi sisi kanannya, bukan? Jadi karena itu ...

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.

Jadi triknya adalah, untuk menghentikan, fharus menghasilkan semacam struktur, sehingga nanti fdapat mencocokkan pola struktur itu dan menghentikan rekursi, tanpa benar-benar peduli tentang "nilai" penuh dari parameternya (?)

Kecuali, tentu saja, Anda ingin melakukan sesuatu seperti membuat daftar tak terbatas, seperti yang diilustrasikan luqui.

Penjelasan faktorial TomMD bagus. Jenis tanda tangan Fix adalah (a -> a) -> a. Dengan kata lain, tipe tanda tangan untuk (\recurse d -> if d > 0 then d * (recurse (d-1)) else 1)adalah . Jadi kita bisa bilang begitu . Dengan begitu, fix mengambil fungsi kita, yaitu , atau sebenarnya, dan akan mengembalikan hasil tipe , dengan kata lain ,, dengan kata lain, fungsi lain!(b -> b) -> b -> b(b -> b) -> (b -> b)a = (b -> b)a -> a(b -> b) -> (b -> b)ab -> b

Tunggu, saya pikir itu seharusnya mengembalikan titik tetap ... bukan fungsi. Ya, semacam (karena fungsi adalah data). Anda dapat membayangkan bahwa ini memberi kita fungsi definitif untuk mencari faktorial. Kami memberinya fungsi yang tidak tahu cara mengulang (maka salah satu parameternya adalah fungsi yang digunakan untuk berulang), dan fixmengajarkannya cara berulang.

Ingat bagaimana saya mengatakan itu f harus menghasilkan semacam struktur sehingga nantinya fpola dapat cocok dan dihentikan? Yah, itu tidak sepenuhnya benar, kurasa. TomMD mengilustrasikan bagaimana kita dapat memperluas xuntuk menerapkan fungsi dan langkah menuju kasus dasar. Untuk fungsinya, dia menggunakan if / then, dan itulah yang menyebabkan terminasi. Setelah penggantian berulang, inbagian dari seluruh definisi fixakhirnya berhenti didefinisikan dalam istilah xdan saat itulah ia dapat dihitung dan lengkap.

Dan Burton
sumber
Terima kasih. Ini adalah penjelasan yang sangat berguna dan praktis.
kizzx2
18

Anda membutuhkan cara agar fixpoint berhenti. Memperluas contoh Anda, jelas itu tidak akan selesai:

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

Berikut adalah contoh nyata saya menggunakan perbaikan (perhatikan saya tidak sering menggunakan perbaikan dan mungkin lelah / tidak khawatir tentang kode yang dapat dibaca ketika saya menulis ini):

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

WTF, katamu! Ya, tapi ada beberapa poin yang sangat berguna di sini. Pertama-tama, fixargumen pertama Anda biasanya harus berupa fungsi yang merupakan kasus 'berulang' dan argumen kedua adalah data untuk bertindak. Berikut adalah kode yang sama dengan fungsi bernama:

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

Jika Anda masih bingung maka mungkin faktorial akan menjadi contoh yang lebih mudah:

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

Perhatikan evaluasinya:

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, apa kamu baru saja melihatnya? Itu xmenjadi fungsi di dalam thencabang kami .

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 -->

Di atas Anda perlu ingat x = f x, maka dua argumen x 2di akhir, bukan hanya 2.

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

Dan saya akan berhenti di sini!

Thomas M. DuBuisson
sumber
Jawaban Anda adalah apa yang sebenarnya fixmasuk akal bagi saya. Jawaban saya sangat bergantung pada apa yang telah Anda katakan.
Dan Burton
@ Thomas, kedua urutan pengurangan Anda salah. :) id xhanya dikurangi menjadi x(yang kemudian dikurangi kembali ke id x). - Kemudian, pada sampel ke-2 ( fact), ketika xthunk pertama kali dipaksa, nilai yang dihasilkan akan diingat dan digunakan kembali. Perhitungan ulang (\recurse ...) xakan terjadi dengan definisi non-sharingy g = g (y g) , bukan dengan definisi sharingfix ini . - Saya telah membuat uji coba di sini - Anda bebas menggunakannya, atau saya dapat mengeditnya jika Anda menyetujuinya.
Will Ness
sebenarnya, ketika fix iddikurangi, let x = id x in xjuga memaksa nilai aplikasi id xdi dalam letframe (thunk), sehingga berkurang menjadi let x = x in x, dan loop ini. Sepertinya begitu.
Will Ness
Benar. Jawaban saya menggunakan penalaran persamaan. Menunjukkan pengurangan a la Haskell, yang menyangkut dirinya sendiri dengan urutan evaluasi, hanya berfungsi untuk membingungkan contoh tanpa keuntungan yang sebenarnya.
Thomas M. DuBuisson
1
Pertanyaannya ditandai dengan haskell dan letrec (mis. Let rekursif, dengan berbagi). Perbedaan antara fixdan Y sangat jelas dan penting di Haskell. Saya tidak melihat kebaikan apa yang disajikan dengan menunjukkan urutan pengurangan yang salah ketika urutan pengurangan yang benar bahkan lebih pendek, lebih jelas dan lebih mudah diikuti, dan mencerminkan dengan benar apa yang sebenarnya terjadi.
Will Ness
3

Bagaimana saya memahaminya adalah, ia menemukan nilai untuk fungsi tersebut, sehingga ia mengeluarkan hal yang sama dengan yang Anda berikan. Tangkapannya adalah, ia akan selalu memilih undefined (atau loop tak terbatas, dalam haskell, loop tak terdefinisi dan tak terbatas adalah sama) atau apa pun yang paling tak terdefinisi di dalamnya. Misalnya, dengan id,

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

Seperti yang Anda lihat, undefined adalah titik tetap, jadi fixpilihlah itu. Jika Anda malah melakukan (\ x-> 1: x).

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

Jadi fixtidak bisa memilih undefined. Untuk membuatnya sedikit lebih terhubung ke loop tak terbatas.

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

Sekali lagi, ada sedikit perbedaan. Jadi apa titik tetapnya? Mari kita coba 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

Sama! Karena ini adalah satu-satunya titik tetap, fixharus menyelesaikannya. Maaf fix, tidak ada loop tanpa batas atau tidak ditentukan untuk Anda.

PyRulez
sumber