Ini adalah latihan yang bagus untuk menjadi lebih fasih dalam bahasa pemrograman yang ingin Anda pelajari, tetapi hanya sedikit mengutak-atiknya. Ini melibatkan bekerja dengan objek, menggunakan atau mensimulasikan penutupan, dan meregangkan sistem tipe.
Tugas Anda adalah menulis kode untuk mengelola daftar malas, lalu menggunakannya untuk mengimplementasikan algoritma ini untuk menghasilkan angka Fibonacci:
Sampel kode ada di Haskell
let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
in take 40 fibs
Hasil:
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986]
Implementasi daftar malas Anda harus memenuhi pedoman ini:
- Node Daftar adalah salah satu dari tiga hal:
- Nihil - Daftar kosong.
[]
- Kontra - Satu item, dipasangkan dengan Daftar item yang tersisa:
1 : [2,3,4,5]
(:
adalah operator kontra di Haskell) - Thunk - Perhitungan tangguhan yang menghasilkan node Daftar saat diperlukan.
- Nihil - Daftar kosong.
- Ini mendukung operasi berikut:
- nil - Buat daftar kosong.
- kontra - Membangun sel kontra.
- thunk - Construct a Thunk, diberi fungsi yang tidak membutuhkan argumen dan mengembalikan Nil atau Kontra.
- force - Diberikan simpul Daftar:
- Jika Nil atau Kontra, kembalikan saja.
- Jika itu Thunk, panggil fungsinya untuk mendapatkan Nil atau Kontra. Ganti thunk dengan Nil atau Kontra itu, dan kembalikan.
Catatan: Mengganti thunk dengan nilai paksa adalah bagian penting dari definisi "malas" . Jika langkah ini dilewati, algoritma Fibonacci di atas akan terlalu lambat.
- kosong - Lihat apakah simpul Daftar adalah Nil (setelah memaksanya).
- head (aka "car") - Dapatkan item pertama dari daftar (atau cocokkan jika nihil).
- tail (alias "cdr") - Dapatkan elemen setelah kepala daftar (atau melempar jika itu nol).
- zipWith - Diberikan fungsi biner (misalnya
(+)
) dan dua (mungkin tak terbatas) daftar, menerapkan fungsi tersebut ke item yang sesuai dari daftar. Contoh:
zipWith (+) [1,2,3] [1,1,10] == [2,3,13]
- take - Diberikan nomor N dan daftar (mungkin tak terbatas), ambil item N pertama dari daftar.
- print - Cetak semua item dalam daftar. Ini harus bekerja secara bertahap ketika diberi daftar panjang atau tidak terbatas.
fibs
menggunakan dirinya sendiri dalam definisi sendiri. Menyiapkan rekursi malas agak rumit; Anda harus melakukan sesuatu seperti ini:- Alokasikan thunk untuk
fibs
. Biarkan dalam kondisi dummy untuk saat ini. - Tentukan fungsi thunk, yang tergantung pada referensi ke
fibs
. - Perbarui thunk dengan fungsinya.
Anda mungkin ingin menyembunyikan pipa ledeng ini dengan mendefinisikan fungsi
fix
yang memanggil fungsi Daftar-kembali dengan nilai baliknya sendiri. Pertimbangkan untuk tidur sebentar agar ide ini bisa masuk.- Alokasikan thunk untuk
Polimorfisme (kemampuan untuk bekerja dengan daftar jenis barang apa pun) tidak diperlukan, tetapi lihat apakah Anda dapat menemukan cara untuk melakukannya yang idiomatis dalam bahasa Anda.
- Jangan khawatir tentang manajemen memori. Bahkan bahasa dengan pengumpulan sampah memiliki kecenderungan untuk membawa benda-benda yang tidak akan pernah Anda gunakan lagi (misalnya pada tumpukan panggilan), jadi jangan heran jika program Anda membocorkan memori saat melintasi daftar yang tak terbatas.
Jangan ragu untuk menyimpang sedikit dari pedoman ini untuk mengakomodasi rincian bahasa Anda, atau untuk mengeksplorasi pendekatan alternatif untuk daftar malas.
Aturan:
- Pilih bahasa yang tidak Anda kenal dengan baik. Saya tidak bisa "meminta" ini, karena itu tag "sistem kehormatan". Namun, pemilih dapat memeriksa riwayat Anda untuk melihat bahasa apa yang Anda posting.
Jangan gunakan dukungan daftar malas bawaan bahasa Anda untuk melakukan semuanya. Posting sesuatu yang substansial atau setidaknya menarik.
Haskell cukup banyak keluar. Yaitu, kecuali Anda melakukan sesuatu seperti ini:
data List a = IORef (ListNode a) data ListNode a = Nil | Cons !a !(List a) | Thunk !(IO (ListNode a))
Catatan: Evaluasi Haskell yang tidak ketat tidak terlarang, tetapi implementasi daftar malas Anda tidak seharusnya menurunkan kemampuannya langsung dari sana. Bahkan, akan menarik untuk melihat solusi yang efisien, murni fungsional yang tidak memerlukan kemalasan.
Python:
- Jangan gunakan itertools.
- Generator baik-baik saja, tetapi Anda menggunakannya, Anda harus menemukan beberapa cara untuk menghafal nilai yang dipaksakan.
sumber
zipWith
dua daftar dengan panjang yang berbeda?zipWith (+) [1,2,3,4,5] [0,0,0] == [1,2,3]
,. Namun, ini tidak masalah untuk algoritma Fibonacci di atas, karena kedua argumen untuk zipWith adalah daftar tanpa batas.fibs
dengan benar, karena itu tergantung pada dirinya sendiri. Saya memperbarui pertanyaan untuk menguraikan rekursi malas. FUZxxl menemukan jawabannya sendiri.Jawaban:
Nota bene
Saya sudah pernah bermain dengan PostScript sebelumnya , tetapi saya tidak akan mengatakan bahwa saya mengetahuinya dengan sangat baik (pada kenyataannya, tebakan saya adalah Anda dapat menghitung jumlah orang di dunia yang benar-benar tahu PostScript menggunakan satu tangan).
Saya menyimpang dari spek Anda bahwa fungsi yang digunakan untuk membuat thunk diizinkan untuk mengembalikan thunk lain;
force
akan terus mengevaluasi sampai hasilnya adalah anil
atau acons
.Daftar diimplementasikan sebagai kamus:
Kode berikut. Perhatikan bahwa kami menimpa beberapa operator builtin (khususnya
print
; Saya belum memeriksa jika ada lebih banyak); dalam penggunaan dunia nyata, ini harus diperhatikan. Tentu saja tidak akan ada penggunaan dunia nyata, jadi itu tidak masalah.Komentar sebelum prosedur harus dibaca sebagai
yaitu menunjukkan isi tumpukan yang diharapkan sebelum panggilan dan isi tumpukan yang dihasilkan setelah panggilan. Komentar dalam prosedur menunjukkan konten tumpukan setelah baris tertentu dieksekusi.
Muat ini ke Ghostscript, abaikan halaman yang ditampilkan - kami hanya bekerja dengan penerjemah. Inilah algoritma Fibonacci:
Dua fungsi menarik tambahan:
Mulai menghitung pada 5, kalikan setiap elemen dari daftar yang dihasilkan dengan 3, dan tampilkan sepuluh nilai pertama:
Mengenai polimorfisme: Meskipun PostScript diketik dengan kuat, ini memungkinkan jenis yang sewenang-wenang sebagai nilai kamus, sehingga Anda dapat memasukkan apa pun yang Anda suka:
Perhatikan bahwa kesalahan ketik, misalnya dari mencoba menambahkan string ke angka, hanya akan terjadi pada waktu evaluasi:
sumber
force
memoize mengembalikan nilai?copy
operator menyalin konten dari versi yang dievaluasi ke dalam dokumen asli, menimpa/type
dan mungkin menetapkan nilai-nilai lain. Setelah mengevaluasi secara rekursif sampai kami memilikinil
ataucons
, itu juga (melaluiundef
) menghapus/func
dan, jika berlaku/data
,. Langkah terakhir tidak sepenuhnya diperlukan (/func
dan/data
hanya akan diabaikan), tetapi meninggalkan langkah ini akan membocorkan lebih banyak memori :)C
Saya seorang pemula total dalam C, kode ini sebenarnya adalah hal nyata pertama yang saya kodekan dalam C. Ini mengkompilasi tanpa peringatan dan berjalan dengan baik pada sistem saya.
Cara membangun
Pertama, dapatkan tarball dari server saya . Ini termasuk makefile, jadi jalankan saja
make
untuk membangunnya dan kemudianmake run
jalankan. Program kemudian mencetak daftar dari 93 angka fibonacci pertama. (Setelah angka 94, bilangan bulat 64 bit yang tidak ditandatangani)Penjelasan
Inti program adalah file
lazy-list.c
. Dalam file header yang sesuai, saya mendefinisikan structlist
, itu adalah daftar malas kami. Ini terlihat seperti ini:Anggota
kind
semacam tag. Itu menandai, apakah kita mengisi ulang daftar akhir (NIL
), sel yang sudah dievaluasi (CONS
) atau thunk (THUNK
). Kemudian, ada yang mengikuti persatuan. iniKonten serikat dinyatakan oleh tag. Jika tagnya adalah
NIL
, konten serikat tidak ditentukan.Dengan mendefinisikan fungsi pembantu yang disebutkan dalam spesifikasi di atas, orang biasanya dapat mengabstraksi definisi daftar dari penggunaannya, misalnya. Anda cukup menelepon
nil()
untuk mendapatkan daftar kosong alih-alih membuatnya sendiri.Tiga fungsi yang paling menarik adalah
zipWith
,take
danfibonaccis
. Tetapi saya tidak ingin menjelaskantake
, karena sangat miripzipWith
. Semua fungsi, yang beroperasi dengan malas, memiliki tiga komponen:Dalam hal
zipWith
inizipWith
,__zipWith
dan__zipArgs
. Saya hanya menunjukkannya di sini tanpa penjelasan lebih lanjut, fungsinya harus cukup jelas:Fungsi menarik lainnya adalah
fibonaccis()
. Masalahnya adalah, kita perlu melewatkan pointer dari sel pertama dan kedua ke thunk ketiga, tetapi untuk membuat sel-sel itu kita juga perlu pointer ke thunk. Untuk mengatasi masalah itu, saya mengisi pointer ke thunk dengan yangNULL
pertama dan mengubahnya menjadi thunk, setelah itu dibuat. Berikut adalah pendengarannya:Kemungkinan peningkatan
content_t
, yang dapat diubah ke apa pun yang cocok.sumber
void*
sebagai tipecontent_t
.void*
juga, tapi saya pikir itu akan menghindari sistem tipe terlalu jauh. Apakah itu tidak mungkin menggunakan templat?void*
dan teman-teman.kind
adalah jenis tag.” Anda hanya bisa menyebutnyatag
, karena itu istilah yang cukup diterima untuk konsep (misalnya tagged union , bertulang tagless G-mesin . Di sisi lain, "semacam" memiliki arti yang berbeda dalam Konteks Haskell: tipe dari suatu tipe.Int
Memiliki jenis*
,[]
jenis* -> *
, dan(,)
jenis* -> * -> *
.C ++
Ini adalah hal terbesar yang pernah saya tulis dalam C ++. Saya biasanya menggunakan Objective-C.
Ini polimorfik tetapi tidak pernah membebaskan apa pun.
main
Fungsi saya (danadd
fungsi untukZipWith
) akhirnya terlihat seperti ini:Ini memberi
Kelas bekerja seperti ini:
Sumber lengkap: di sini . Ini berantakan, terutama karena itu dalam satu file besar.
Sunting: mengubah tautan (yang lama sudah mati).
sumber
()
operator), dan menggunakan warisan untuk menghindari keharusan menggunakanvoid*
. Lihat di sini untuk contoh sepele dari melakukan itu.Python
Tidak menggunakan generator untuk mengimplementasikan daftar, hanya untuk mengimplementasikan
__iter__
metode yang digunakanfor
.Daftar Fibonacci dibuat seperti ini:
sumber
self.__class__ = node.__class__
. Perhatikan bahwa ini mencapai pengecualian NotImplemented ketika mencapai hingga 2971215073 (panjang), yang tampaknya merupakan argumen yang tidak valid untuk int .__ add__. Untuk mendukung bilangan bulat besar, lakukanfib.append(fib.zip_with(lambda a,b: a+b, fib.tail()))
Rubi
Program Ruby pertama saya. Kami mewakili semua node sebagai array, di mana panjang array menentukan jenisnya:
Kode ini kemudian cukup mudah, dengan hack untuk mengatur ulang fungsi thunk untuk mengatur rekursif.
sumber
[...]
bukanArray[...]
.Google Go
Bahasa yang relatif baru, dan saya mempelajarinya dengan
CTRL+F
menggunakan Spec .Masalahnya telah diperbaiki, dengan berurusan dengan thunk-in-a-thunks. Namun, tampaknya kompiler online tidak dapat mengambil 40 elemen, mungkin karena memori. Saya akan mengujinya di Linux saya nanti.
Saya menguji kode dengan kompiler online , karena saya tidak dapat menginstal Go di Windows dengan mudah.
sumber
iota
generator konstan. Lihat contoh di Spesifikasi Bahasa Pemrograman Go , dan jawaban di StackOverflow .Fibs
fungsi tidak bekerja karena Go menggunakan evaluasi yang ketat, danFibs
recurses pada dirinya sendiri tanpa kondisi terminating.Fibs0
SayaFibs1
menggunakan pendekatan generator sederhana daripada algoritma yang dijelaskan dalam posting saya, sehingga tidak memenuhi "persyaratan". Saya memperbarui posting saya untuk menguraikan rekursi malas, yang diperlukan untuk mengimplementasikanfibs = 0 : 1 : zipWith (+) fibs (tail fibs)
.Cons(0, Cons(1, ZipWith(Plus, Thunk(Fibs), Tail(Thunk(Fibs)))))
, itu hilang dari ingatanCons(0, Cons(1, Thunk(func() List { return ZipWith(Plus, Thunk(Fibs), Thunk(func() List { return Tail(Fibs()) })) })))
dan saya mendapatkan kesalahan alamat memori yang tidak validKristal
Meskipun mengikuti repositori GitHub, saya belum pernah menggunakan Crystal sampai sekarang. Crystal adalah varian Ruby yang diketik secara statis dengan inferensi tipe penuh. Meskipun sudah ada jawaban Ruby, pengetikan statis Crystal membuat saya menggunakan polimorfisme, dan bukan array, untuk mewakili node. Karena Crystal tidak mengizinkan modifikasi
self
, saya membuat kelas pembungkus, bernamaNode
, yang akan membungkus segala sesuatu yang lain dan mengelola pemukul.Seiring dengan kelas, saya menciptakan fungsi konstruktor
lnil
,cons
danthunk
. Saya juga tidak pernah menggunakan Ruby lebih dari 20 script sebelumnya, jadi hal-hal yang menghalangi saya cukup sedikit.Saya mendasarkan
fib
fungsi dari jawaban Go .sumber
Saya membengkokkan aturan sedikit karena belum ada solusi .NET di sini - atau lebih umum solusi OOP kecuali untuk yang di Python yang menggunakan warisan, tapi itu cukup berbeda dari solusi saya untuk membuat keduanya menarik (khususnya sejak Python memungkinkan untuk memodifikasi
self
instance, membuat implementasi thunk secara langsung).Jadi ini C # . Pengungkapan penuh: Saya tidak berada di dekat seorang pemula di C # tetapi saya belum menyentuh bahasa dalam beberapa saat karena saya saat ini tidak menggunakannya di tempat kerja.
Poin yang menonjol:
Semua kelas (
Nil
,Cons
,Thunk
) berasal dari kelas dasar umum abstrak,List
.The
Thunk
kelas menggunakan Amplop-Surat pola. Ini pada dasarnya mengemulasiself.__class__ = node.__class__
tugas dalam sumber Python, karenathis
referensi tidak dapat dimodifikasi dalam C #.IsEmpty
,Head
danTail
sifat.Semua fungsi yang sesuai diimplementasikan secara rekursif dan malas (kecuali untuk
Print
, yang tidak bisa malas) dengan mengembalikan thunks. Sebagai contoh, ini adalahNil<T>.ZipWith
:... dan ini adalah
Cons<T>.ZipWith
:Sayangnya, C # tidak memiliki banyak pengiriman, kalau tidak saya juga bisa menghilangkan
if
pernyataan itu. Sayangnya, tidak ada dadu.Sekarang, saya tidak begitu senang dengan implementasi saya. Saya senang sejauh ini karena semua hal di atas benar-benar mudah. Tapi . Saya merasa bahwa definisi
Fib
rumit tidak perlu karena saya perlu membungkus argumen menjadi thunks untuk membuatnya bekerja:(Di sini,
List.Cons
,List.Thunk
danList.ZipWith
adalah kemudahan pembungkus.)Saya ingin memahami mengapa definisi yang jauh lebih mudah berikut tidak berfungsi:
diberikan definisi yang tepat
Concat
, tentu saja. Ini pada dasarnya adalah apa yang dilakukan kode Python - tetapi tidak berfungsi (= melempar kecocokan)./ EDIT: Joey telah menunjukkan kelemahan yang jelas dalam solusi ini. Namun, mengganti baris kedua dengan thunk juga menghasilkan kesalahan (Mono segfaults; Saya curiga stack overflow yang tidak ditangani dengan baik oleh Mono):
Kode sumber lengkap dapat ditemukan sebagai inti di GitHub .
sumber
fib.ZipWith
danfib.Tail
gunakan yang lamafib
, yang tetap[0,1]
dan tidak berubah. Dengan demikian, Anda mendapatkan[0,1,1]
(saya pikir), danTake
fungsi Anda tidak membiarkan Anda mengambil dari nol ( take Haskell tidak, meskipun). Cobalah membungkus nilai baris kedua dalam sebuah pukulan, jadi itu akan merujuk pada yang barufib
daripada yang lama.Pico
sebagai catatan, solusi ini menggunakan terjemahan gaya tunda skema sebagaimana didefinisikan dalam srfi-45 . dan membangun daftar malas di atas itu.
output terlihat seperti ini: (tapi tergantung pada bagaimana
tpico
. ditambal mungkin memiliki tanda kutip lebih ganda di dalamnyadisplay
. biasanya mencetak string dengan tanda kutip yaitu semua penampilan dari[
,,
,]
akan memiliki kutipan di sekitar mereka seperti"["
.)karena batas-batas dari tipe data integer dalam tpico ini gagal menghitung angka Fibonacci 45 (atau offset ke-46).
perhatikan bahwa tpico 2.0pl11 rusak dalam hal itu
begin(a,b)
(yang biasanya ditulis sebagai{a;b}
) danif
fungsinya tidak rekursif ekor. belum lagi bahwa saya butuh 5 tahun untuk mencari tahu mengapabegin
tidak ekor rekursif. juga pada waktu itu saya menulis terjemahan srfi-45 di Pico. ternyata yangbegin
menunggu nilainyab
sebelum kembali ketika tidak perlu menunggu. dan begitu saya mendapatkannya saya juga bisa memperbaikiif
karena memiliki masalah yang sama. dan ada kesalahan lain ini yang membuat konstruktor meta levelmake
tidak beroperasi.Pico memungkinkan kontrol fungsi jika argumennya dievaluasi sebelum fungsi dipanggil atau hanya dikemas sebagai thunks. untuk kode ini, saya dapat ellipsis atas keanehan panggilan dengan fungsi .
Pico tidak memiliki tipe inferensi. Saya berpikir tentang ini untuk sementara waktu tetapi saya berlari ke masalah karena keanehan panggilan dengan fungsi . saya datang dengan pernyataan bahwa jenis harus menyandikan keberadaan nama variabel terikat . tetapi saya terutama berpikir tentang bagaimana menyesuaikan inferensi tipe Hindley-Milner ke subset dari Pico tanpa mutasi. ide utamanya adalah bahwa pemeriksa tipe mengembalikan beberapa skema yang mungkin jika ada lebih dari satu kemungkinan pengikatan dan tipe check berhasil jika setidaknya ada satu skema tipe yang mungkin . skema yang mungkin adalah skema yang tidak ada jenis konflik tugas.
sumber