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?
haskell
fixpoint-combinators
letrec
Jason Baker
sumber
sumber
fix error
ghci dan merasa nyaman dengan diri Anda sendiri."fix
sebagaifix f = f (fix f)
. Singkat, sederhana, berhasil, dan identik dengan definisi matematika.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.fix
! membantu saya memahamifix
banyak hal.Jawaban:
Anda tidak melakukan kesalahan apa pun.
fix id
adalah lingkaran tak terbatas.Ketika kami mengatakan bahwa
fix
mengembalikan titik tetap terkecil dari suatu fungsi, yang kami maksudkan dalam pengertian teori domain . Jadifix (\x -> 2*x-1)
ini tidak akan kembali1
, karena meskipun1
merupakan 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,
fix
adalah 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 menggunakanfix
:fix (\x -> 1:x)
(Atau sederhananya
fix (1:)
), yang mengatakan carikan saya titik tetap dari(1:)
fungsi, IOW nilaix
sedemikian rupa sehinggax = 1:x
... seperti yang kami definisikan di atas. Seperti yang Anda lihat dari definisi,fix
tidak 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
fix
cara ini:fib = fix (\f -> \n -> if n < 2 then n else f (n-1) + f (n-2))
Latihan: perluas definisi
fix
untuk menunjukkan bahwa kedua definisifib
ini setara.Tetapi untuk pemahaman penuh, baca tentang teori domain. Ini hal yang sangat keren.
sumber
fix id
:fix
mengambil fungsi tipea -> a
dan mengembalikan nilai tipea
. Karenaid
polimorfik untuk apapuna
,fix id
akan memiliki tipea
, 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. Jadifix id
menghasilkan persis apa yang seharusnya, nilai bawah. Bahayanyafix
adalah jika ⊥ adalah titik tetap dari fungsi Anda, maka menurut definisi titik tetap terkecil , oleh karena itufix
tidak akan berhenti.undefined
juga merupakan nilai jenis apa pun. Anda dapat menentukanfix
sebagai:fix f = foldr (\_ -> f) undefined (repeat undefined)
._Y f = f (_Y f)
.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 yangx
didefinisikan sebagaif x
. Tapi pikirkan sebentar.x = f x
Karena x = fx, maka kita bisa mengganti nilai
x
di 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,
f
harus menghasilkan semacam struktur, sehingga nantif
dapat 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)
a
b -> 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
fix
mengajarkannya cara berulang.Ingat bagaimana saya mengatakan itu
f
harus menghasilkan semacam struktur sehingga nantinyaf
pola dapat cocok dan dihentikan? Yah, itu tidak sepenuhnya benar, kurasa. TomMD mengilustrasikan bagaimana kita dapat memperluasx
untuk menerapkan fungsi dan langkah menuju kasus dasar. Untuk fungsinya, dia menggunakan if / then, dan itulah yang menyebabkan terminasi. Setelah penggantian berulang,in
bagian dari seluruh definisifix
akhirnya berhenti didefinisikan dalam istilahx
dan saat itulah ia dapat dihitung dan lengkap.sumber
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,
fix
argumen 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
x
menjadi fungsi di dalamthen
cabang 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 argumenx 2
di akhir, bukan hanya2
.let x = ... in 3 * (\d -> if d > 0 then d * (x (d-1)) else 1) 2 -->
Dan saya akan berhenti di sini!
sumber
fix
masuk akal bagi saya. Jawaban saya sangat bergantung pada apa yang telah Anda katakan.id x
hanya dikurangi menjadix
(yang kemudian dikurangi kembali keid x
). - Kemudian, pada sampel ke-2 (fact
), ketikax
thunk pertama kali dipaksa, nilai yang dihasilkan akan diingat dan digunakan kembali. Perhitungan ulang(\recurse ...) x
akan 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.fix id
dikurangi,let x = id x in x
juga memaksa nilai aplikasiid x
di dalamlet
frame (thunk), sehingga berkurang menjadilet x = x in x
, dan loop ini. Sepertinya begitu.fix
dan 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.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
fix
pilihlah 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
fix
tidak 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,
fix
harus menyelesaikannya. Maaffix
, tidak ada loop tanpa batas atau tidak ditentukan untuk Anda.sumber