Semuanya adalah Fungsi: Ulasan kuliah David Beazley dan SICP
(ezzeriesa.notion.site)Ulasan kuliah David Beazley dan SICP: pengalaman selama 1 minggu
Saya membagikan pengalaman mengikuti kuliah SICP oleh David Beazley pada akhir 2022. Meski ada banyak materi gratis, kuliah Dave sangat efektif karena memilih topik-topik tertentu dan menjelaskannya secara mendalam.
Titik awal
Kuliah SICP menggunakan bahasa Scheme, dan di sini dijelaskan konsep dasar model substitusi dengan mengimplementasikan interpreter Scheme sederhana dalam Python.
Dasar-dasar bahasa Scheme
- Primitive: nilai-nilai dasar (misalnya bilangan bulat)
- Operator: operasi dasar seperti
+,-,*,/menggunakan notasi prefiks - define: definisi variabel
> (define x 2)
> (+ x 3) ; hasil: 5
- if: pernyataan kondisi
- lambda: definisi fungsi anonim
> ((lambda (x) (* x x)) 3) ; hasil: 9
Interpreter Scheme di Python
Mengimplementasikan interpreter sederhana untuk mengevaluasi kode Scheme dengan Python. Operasi dasar didefinisikan sebagai fungsi Python.
definitions = {
"+": lambda x, y: x + y,
"*": lambda x, y: x * y,
}
Contoh:
> evaluate(("+", 2, 3)) # hasil: 5
Juga mencakup implementasi define dan lambda, serta penanganan pernyataan kondisi if.
Model substitusi (Substitution Model)
Model substitusi adalah cara sederhana untuk menafsirkan program, dengan mengevaluasinya sambil mengganti variabel dengan nilainya. Namun, model ini gagal ketika assignment disertakan.
Status (State)
Contoh yang membuat model substitusi runtuh adalah assignment. Misalnya, saat memodelkan saldo rekening bank, set! digunakan untuk memperbarui variabel.
(define balance 100)
(define (withdraw amount)
(set! balance (- balance amount))
balance)
Dalam kasus ini, model substitusi tidak dapat membedakan keadaan saldo sebelum dan sesudahnya.
Model environment pun menjadi diperlukan. Variabel didefinisikan di dalam environment, dan setiap prosedur memiliki environment-nya sendiri.
Stream
Ada juga stream sebagai cara lain untuk memodelkan status. Stream dapat memodelkan nilai masa depan melalui evaluasi malas (lazy evaluation).
Loop tak berhingga dan urutan evaluasi
Perbedaan urutan evaluasi: sebagian besar bahasa menggunakan applicative-order evaluation dengan mengevaluasi argumen terlebih dahulu.
> (square (+ 1 2)) ; hasil: 9
Namun, normal-order evaluation menunda evaluasi hingga argumen benar-benar diperlukan. Karena itu, loop tak berhingga bisa dihindari.
> (define (p) (p))
> (define (test x y) (if (= x 0) 0 y))
> (test 0 (p)) ; pada normal-order mengembalikan 0, pada applicative-order menjadi loop tak berhingga
Lambda calculus dan bilangan Church
Melalui Church encoding, angka dapat direpresentasikan sebagai prosedur. Ini adalah konsep penting dalam pemrograman fungsional.
(define (zero f) (lambda (x) x))
(define (increment n) (lambda (f) (lambda (x) (f ((n f) x)))))
zeroadalah fungsi yang mengembalikan argumennya apa adanya (fungsiidentity).incrementmenerapkan pemanggilan fungsi satu kali lagi.
Contoh
> ((zero (lambda (x) (+ x 1))) 0) ; hasil: 0
> (((increment zero) (lambda (x) (+ x 1))) 0) ; hasil: 1
Iterasi vs rekursi
Scheme menggunakan rekursi alih-alih for loop untuk melakukan pekerjaan berulang.
Contoh rekursi: faktorial
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
Pemanggilan rekursif ini dapat menggunakan stack dan memakan banyak memori.
Optimisasi tail-call
Scheme mengurangi penggunaan memori melalui optimisasi tail-call. Karena itu, ia bekerja seperti proses iteratif.
(define (factorial n)
(define (iter product counter)
(if (> counter n)
product
(iter (* product counter) (+ counter 1))))
(iter 1 1))
Penutup
Kuliah David Beazley membahas konsep-konsep utama SICP dengan memilih topik-topik tertentu lalu mengupasnya secara mendalam. Secara khusus, kuliah ini membantu memahami beragam paradigma pemrograman seperti pemrograman fungsional, lambda calculus, dan urutan evaluasi.
Kutipan Knuth
Jika Anda hanya mempelajari teori, itu berarti sudah waktunya berfokus pada sisi praktis; dan jika Anda hanya praktik, itu berarti sudah waktunya berfokus pada sisi teoretis.
1 komentar
Pendapat Hacker News
Sebagai peringatan, jika Anda mendalami SICP/Lisp/Scheme, cara Anda memikirkan pemrograman bisa berubah, dan rangsangan intelektual seperti itu selalu patut disambut
Namun jika ide-ide itu diterapkan mentah-mentah ke codebase berorientasi objek, biasanya hasilnya kontraproduktif atau memicu penolakan dari anggota tim
Misalnya setelah Lisp, Anda mungkin tergoda mengganti semua loop
formenjadiforEach, atau menjadikan semuanya rangkaianmap/reduce; tetapi jika bahasanya tidak sepenuhnya merangkul pemrograman fungsional, itu bisa merusak keterbacaan dan performa sekaligusPada akhirnya, mengingat bahwa memori yang dapat berubah dan CPU-lah yang memproses kode akan memberi pijakan; dan sekarang saya melihat desain berorientasi data serta “empati mekanis” yang selaras dengan realitas hardware sebagai sesuatu yang sehari-hari lebih praktis daripada konsep abstrak seperti bilangan Church
Semakin sedikit efek samping, semakin mudah diprediksi; dan orientasi objek sering dipakai bahkan di tempat yang cukup dengan fungsi saja
Dalam pengembangan game, ada banyak komputasi dan kode berisiko sehingga orientasi objek cukup cocok, tetapi dalam pengembangan web, pemrograman fungsional jauh lebih alami; di SaaS, bahasa seperti Elixir memungkinkan kita menulis kode yang lebih andal, lebih sedikit bug, dan lebih mudah diuji
Selain itu, hal-hal seperti
forEachdanmap/reducesudah ada di koleksi Smalltalk, dan meski bukan sintaks kelas satu, juga disalin ke Object Pascal dan C++Karena memori dasarnya dapat berubah, bahasa-bahasa keluarga ML pun memiliki mekanisme untuk menangani mutasi saat benar-benar diperlukan
goto, tetapigotobahkan kurang populer daripada LispYang lebih aneh, untuk mendapatkan performa di CPU modern, cache itu esensial, tetapi belum ada bahasa yang memperlakukan hierarki memori sebagai warga kelas satu
Yang paling mendekati tampaknya adalah C gaya Linux yang dipenuhi garis bawah seperti lautan
Memberikan pengantar yang baik tentang mengenkode state dengan fungsi murni
Sebenarnya ada jauh lebih banyak encoding fungsional murni untuk segala jenis data, seperti tree, integer, tipe sum/product, image, monad, dan sebagainya
Encoding-nya bisa sedikit membingungkan, tetapi sekaligus elegan dan kecil
Misalnya, jika mengimplementasikan monad Maybe secara fungsional di JavaScript, jadinya seperti ini:
Nothing = nothing => just => nothingJust = v => nothing => just => just(v)pure = Justbind = mx => f => mx(mx)(f)evalMaybe = maybe => maybe("Nothing")(v => "Just " + v)console.log(evalMaybe(bind(Nothing)(n => pure(n + 1)))) // Nothingconsole.log(evalMaybe(bind(Just(42))(n => pure(n + 1)))) // Just 43data Maybe a = Nothing | Just afoldMaybe :: (Unit -> r) -> (a -> r) -> Maybe a -> rDua fungsi tingkat tinggi yang diberikan ke
foldMaybemasing-masing berkorespondensi denganNothingdanJustNamun untuk sisi
Nothing, bentuk ini menambahkan parameterUnitagar sedikit lebih tepatDalam teori tipe ini cukup keren, tetapi tidak terlalu bagus untuk pemrograman nyata
Dulu saya pernah menonton kuliah SICP yang sebenarnya, yaitu rekaman MIT OCW tahun 1986
Kuliah itu sering dipuji karena kepadatan informasinya tinggi, tetapi kenyataannya cukup banyak waktu terbuang untuk tanya jawab mahasiswa, waktu ketika pengajar mengalihkan perhatian ke upaya presentasi “multimedia” di ruang kuliah, serta bagian-bagian ketika rencana keseluruhan perkuliahan belum sepenuhnya tersusun sebelumnya sehingga tanya jawab tidak bisa dicegah secara proaktif
Waktu untuk menulis di papan tulis juga, jika diakumulasi, cukup besar
Tentu saja urutan materi bisa diperdebatkan dan disusun ulang sesuka hati, dan suatu saat saya juga berencana membuat sendiri seri video yang menjelaskan materi ini sesuai dengan intuisi saya
Saya senang kuliah ini tampaknya tetap mempertahankan akarnya sambil memakai bahasa yang lebih modern seperti Python, dan menurut saya, karena Python adalah bahasa multiparadigma yang praktis, orang belum cukup mengakui daya ekspresinya melalui idiom fungsional, meskipun bukan kemurnian penuh
Menurut saya Python bisa, dan seharusnya, dikeluarkan dari kuliah ini
Dukungan Python untuk pemrograman fungsional sangat lemah
List-nya tidak berbasis cons, lambda-nya banyak batasan, pattern matching-nya buruk dan tidak berbasis ekspresi, serta namespace-nya juga aneh
Python juga sulit disebut bahasa modern; ia masih tertahan di era 1990-an, dan karena memiliki C-API yang lumayan, sayangnya ia tumbuh dengan mengorbankan bahasa-bahasa yang lebih baik
Karena tidak ada optimisasi tail call, sebagian latihan kode membutuhkan solusi yang sama sekali berbeda dari Scheme
Jika kode yang diterjemahkan 1:1 gagal, apakah pengajar harus mengatakan kepada mahasiswa agar percaya saja karena bahasa yang dipilih membuatnya berperilaku begitu, atau haruskah semuanya dilihat sebagai masalah mengeksternalisasi stack dan diselesaikan seperti itu, saya jadi bertanya-tanya
Memaksakan SICP ke dalam Python terlihat cukup bodoh
Situsnya sekarang offline, tetapi kuliahnya bisa dilihat di archive.org
https://en.m.wikipedia.org/wiki/ArsDigita#ArsDigita_Foundati...
https://archive.org/details/arsdigita_01_sicp/
Mereka menjual USB key berisi seluruh kurikulum; akan sangat bagus kalau ada yang mengunggah ISO-nya
https://web.archive.org/web/20190222145553/aduni.org/drives/
Hal-hal yang bisa diekspresikan dengan sederhana di Scheme menjadi latihan yang rumit dalam bahasa lain
Alih-alih berfokus pada konsep dasarnya, mahasiswa jadi berfokus pada detail bahasa implementasi yang dalam Scheme tidak perlu dipikirkan
Saat pertama kali melihat
cons/car/cdrdiimplementasikan dengan lambda, rasanya seperti sulapNamun pada akhirnya saya melihatnya sebagai demonstrasi bahwa runtime bahasa mengimplementasikan kamus key/value, dan kita bisa meminjam implementasi itu untuk membuat struktur data lain
Di Elixir, kita bisa mengambil dari depan sebanyak yang kita mau
Closure tanpa perilaku hanyalah pointer ke variabel yang tertutup, dan closure dengan 2 pointer adalah pasangan yang memungkinkan kita memperoleh
cardancdrRuntime harus membuat objek yang ditunjuk tetap bisa digunakan di luar definisinya, sehingga diperlukan escape analysis, garbage collection, dan semacamnya, tetapi kamus tidak diperlukan
Baru-baru ini saya menemukan konsep bahwa untuk membuktikan hal seperti
0 != 1dalam theorem proving, Church encoding saja tidak cukup dan diperlukan tipe data induktifSaya sudah menaruh catatan kasar terkait hal itu di sini, bersama kritik terpisah terhadap SICP: https://intellec7.notion.site/Drinking-SICP-hatorade-and-why...
Saya ingin lebih memahami batasan dari sudut pandang bahwa “semuanya hanyalah fungsi”
0 ≠ 1bisa dibuktikanDari teorema
f = g -> f x = g x, kita bisa menyimpulkan untuk membuat fakta ketaksamaan di sisi kanan, lalu mengambil kontraposisinyaPernyataan bahwa ketaksamaan antara bilangan Church tidak bisa dibuktikan langsung tanpa fakta lain tentang ketaksamaan tampaknya benar
Sebaliknya, pada tipe data induktif, sistem pembuktian dapat secara rekursif menghilangkan penerapan konstruktor terluar pada dua instance konkret dari tipe induktif yang sama, lalu secara langsung “mengamati” kesamaan atau ketaksamaan
Saya penasaran apakah itu bisa dicapai dengan Scott encoding atau Church encoding
Bukunya sendiri sudah sedang didiskusikan di sini: https://news.ycombinator.com/item?id=42157558
Saya penasaran apakah ada alasan tautannya mengarah ke diskusi di bagian bawah, bukan ke awal halaman itu
Mungkin tulisan ini bisa digabungkan ke diskusi yang sudah ada
David Beazley adalah sosok yang cukup legendaris di dunia Python, dan kuliah ini awalnya tampak seperti ide yang mengejutkan, tetapi setelah dipikir sekitar 2 detik rasanya seperti kombinasi yang sempurna, jadi saya mendaftar untuk kuliah berikutnya
Intinya, bentuk seperti ini sepertinya akan menjadi wajah pendidikan berkelanjutan untuk software engineer ke depan
Ada typo pada kode di bagian “the substitution model”
("+", ("fib", ("-", "n", 2)), ("fib", ("-", "n", 1))),Yang didefinisikan adalah
fibonacci, danfibtidak didefinisikan, jadi dua pemanggilanfibitu jelas seharusnyafibonacciKode di repositori GitHub aslinya sudah benar: https://github.com/savarin/pyscheme/blob/0f47292c8e5112425b5...
Saya pikir SICP sangat bagus
Namun, semakin banyak saya belajar dan semakin dalam saya mempelajari matematika, saya makin mendekati kesimpulan bahwa relasi adalah konsep primitif yang lebih fundamental
Semua fungsi dapat dinyatakan sebagai bentuk relasi yang terbatas, tetapi kebalikannya tidak berlaku tanpa menambahkan perangkat tambahan yang cukup banyak
Database relasional dan SQL memang contoh pemrograman relasional yang paling dikenal dan sukses, tetapi menurut saya ranah ini masih sangat belum banyak dieksplorasi
Minat saya sekarang lebih condong ke mengajarkan dasar-dasar matematika kepada anak-anak yang sangat kecil daripada desain bahasa pemrograman
Anehnya, mengajarkan predikat seperti “besar” sebagai relasi 1-ary, dan “lebih besar dari” sebagai relasi 2-ary, jauh lebih mudah daripada mencoba menangkap konsep yang sama sebagai fungsi
Saya kurang suka sudut pandang “semuanya adalah fungsi” karena terlalu disederhanakan dan sering kali tidak membantu
Misalnya, ada fungsi yang tidak muat di cache, RAM, disk, dan sebagainya; ada fungsi yang Big-O-nya meledak seperti N-way
JOINatau pencarian/pencocokan; dan ada juga fungsi dengan efek samping, termasuk yang tidak idempotentHampir tidak ada orang yang memikirkan serangan side-channel terhadap fungsi
Ada juga fungsi nondeterministik yang bergantung pada tanggal, waktu, durasi, dan sebagainya, dan fungsi bisa gagal di tengah jalan atau tidak gagal secara elegan
Sulit juga berasumsi bahwa fungsi tidak mengonsumsi resource yang memengaruhi “fungsi” lain yang memakai pool resource bersama
Argumen fungsi bisa berukuran atau berkompleksitas sewenang-wenang, tetapi di dunia nyata ada batasnya; lalu kita membutuhkan pointer, dan kemudian lagi membutuhkan referensi jarak jauh seperti web atau disk
Tolong beri tahu kapan saya harus berhenti, saya masih bisa terus bicara
Dalam kasus ini, model kalkulus lambda adalah dasar dari pendekatan “semuanya hanyalah fungsi”, dan sebagai model komputasi ia sangat sederhana sekaligus lebih mudah ditangani dan dinalar dibandingkan mesin Turing dan sejenisnya
Karena itu, ia menjadi dasar bagi sebagian besar logika komputer dan sistem pembuktian
Jika suatu fungsi membutuhkan resource, buatlah ia meminta resource tersebut; jika bergantung pada tanggal/waktu, buatlah ia bergantung pada tanggal/waktu; jika mengembalikan nilai nondeterministik, buatlah ia mengembalikan nilai nondeterministik
Salah satu alasan pendekatan pemrograman fungsional bersinar adalah karena ia memaksa kita menangani hal-hal seperti ini secara serius
Jika ingin menggunakan resource yang dibagi secara implisit, kita harus memodelkannya, dan “fungsi” yang bergantung pada resource bersama implisit dibedakan secara eksplisit dari fungsi sungguhan
Itu lebih mendekati prosedur
Fungsi adalah prosedur, tetapi tidak semua prosedur adalah fungsi