Her Şey Bir Fonksiyondur: David Beazley ve SICP Dersine Dair Notlar
(ezzeriesa.notion.site)David Beazley ve SICP dersine dair notlar: 1 haftalık deneyim
2022'nin sonunda David Beazley'nin SICP dersine katılma deneyimini paylaşıyorum. Pek çok ücretsiz kaynak var, ancak Dave'in dersi belirli konuları seçip derinlemesine açıklayarak oldukça etkiliydi.
Başlangıç noktası
SICP dersi Scheme diliyle yürütüldü; burada ise temel kavram olan yerine koyma (substitution) modelini açıklamak için Python ile basit bir Scheme yorumlayıcısı geliştirildi.
Scheme dilinin temelleri
- Primitive: temel değerler (ör. tamsayılar)
- Operatörler:
+,-,*,/gibi temel işlemler önek gösterimiyle kullanılır - define: değişken tanımı
> (define x 2)
> (+ x 3) ; sonuç: 5
- if: koşul ifadesi
- lambda: anonim fonksiyon tanımı
> ((lambda (x) (* x x)) 3) ; sonuç: 9
Python'da Scheme yorumlayıcısı
Scheme kodunu değerlendiren basit bir yorumlayıcı Python kullanılarak geliştirildi. Temel işlemler Python fonksiyonları olarak tanımlandı.
definitions = {
"+": lambda x, y: x + y,
"*": lambda x, y: x * y,
}
Örnek:
> evaluate(("+", 2, 3)) # sonuç: 5
Buna define ve lambdanın uygulanışı ile koşul ifadesi ifin işlenmesi de dahildi.
Yerine koyma modeli (Substitution Model)
Yerine koyma modeli, basit programları yorumlamanın bir yoludur; program değerlendirilirken değişkenler değerleriyle değiştirilir. Ancak atama (assignment) işin içine girdiğinde bu model başarısız olur.
Durum (State)
Yerine koyma modelinin bozulmasına örnek olarak atama (assignment) verilebilir. Örneğin bir banka hesabı bakiyesini modellenirken değişkeni güncellemek için set! kullanılır.
(define balance 100)
(define (withdraw amount)
(set! balance (- balance amount))
balance)
Bu durumda yerine koyma modeli, önceki ve sonraki bakiye durumlarını ayırt edemez.
Bu yüzden ortam (Environment) modeline ihtiyaç doğar. Değişkenler ortam içinde tanımlanır ve her prosedürün kendine ait bir ortamı vardır.
Akışlar (Streams)
Durumu modellemenin bir başka yolu da streams kullanmaktır. Streams, tembel değerlendirme (lazy evaluation) sayesinde gelecekteki değerleri de modelleyebilir.
Sonsuz döngü ve değerlendirme sırası
Değerlendirme sırasındaki fark: Çoğu dil, argümanları önce değerlendirerek applicative-order evaluation kullanır.
> (square (+ 1 2)) ; sonuç: 9
Buna karşılık normal-order evaluation, argümanların değerlendirilmesini gerçekten ihtiyaç duyulana kadar erteler. Bu sayede sonsuz döngüden kaçınılabilir.
> (define (p) (p))
> (define (test x y) (if (= x 0) 0 y))
> (test 0 (p)) ; normal order'da 0 döner, applicative order'da sonsuz döngü
Lambda kalkülüsü ve Church sayıları
Church encoding sayesinde sayılar prosedürler olarak ifade edilebilir. Bu, fonksiyonel programlamanın önemli kavramlarından biridir.
(define (zero f) (lambda (x) x))
(define (increment n) (lambda (f) (lambda (x) (f ((n f) x)))))
zero, argümanını olduğu gibi döndüren bir fonksiyondur (identityfonksiyonu).increment, fonksiyon çağrısını bir kez daha uygular.
Örnek
> ((zero (lambda (x) (+ x 1))) 0) ; sonuç: 0
> (((increment zero) (lambda (x) (+ x 1))) 0) ; sonuç: 1
İterasyon vs özyineleme
Scheme, for döngüsü yerine tekrar eden işleri yapmak için özyineleme kullanır.
Özyineleme örneği: faktöriyel
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
Bu özyinelemeli çağrı, stack kullandığı için fazla bellek tüketebilir.
Kuyruk çağrısı optimizasyonu (Tail-call optimization)
Scheme, tail-call optimization sayesinde bellek kullanımını azaltır. Bu da onun yinelemeli (iterative) bir süreç gibi çalışmasını sağlar.
(define (factorial n)
(define (iter product counter)
(if (> counter n)
product
(iter (* product counter) (+ counter 1))))
(iter 1 1))
Kapanış
David Beazley'nin dersi, SICP'nin ana kavramlarını seçip derinlemesine ele alıyor. Özellikle fonksiyonel programlama, lambda kalkülüsü ve değerlendirme sırası gibi farklı programlama paradigmalarını anlamaya yardımcı oluyor.
Knuth'tan alıntı
Yalnızca teori çalışıyorsanız, bu pratik tarafa odaklanma zamanınızın geldiği anlamına gelir; yalnızca pratik yapıyorsanız da teoriye odaklanma zamanınızın geldiği anlamına gelir.
1 yorum
Hacker News yorumları
Uyaralım: SICP/Lisp/Scheme dünyasına derinlemesine girerseniz programlamayı düşünme biçiminiz değişebilir; böyle bir zihinsel uyarım da her zaman memnuniyetle karşılanır.
Ancak bu fikirleri nesne yönelimli bir kod tabanına olduğu gibi uygularsanız çoğu zaman ters teper ya da ekip arkadaşlarının tepkisini çeker.
Örneğin Lisp’ten sonra tüm
fordöngüleriniforEachile değiştirmek ya da her şeyimap/reducezincirlerine çevirmek isteyebilirsiniz; ama dil fonksiyonel programlamayı tam anlamıyla benimsemiyorsa bu, hem okunabilirliğe hem de performansa zarar verebilir.Sonuçta değiştirilebilir bellek ve CPU’nun kodu işlediğini hatırlamak insanı zeminde tutar; günümüzde Church sayıları gibi soyut kavramlardan çok, veri odaklı tasarım ve donanım gerçeklerine göre “mekanik empati”nin günlük hayatta daha pratik olduğunu düşünüyorum.
Yan etkiler ne kadar azsa o kadar öngörülebilir olur; nesne yönelimlilik de yalnızca fonksiyonların yeterli olduğu yerlerde bile sıkça kullanılıyor.
Oyun geliştirmede çok sayıda hesaplama ve riskli kod olduğundan nesne yönelimlilik oldukça iyi oturur; ancak web geliştirmede fonksiyonel programlama çok daha doğal, SaaS’ta ise Elixir gibi diller daha güvenilir, daha az hatalı ve test etmesi daha kolay kod yazdırır.
Üstelik
forEach,map/reducegibi şeyler Smalltalk koleksiyonlarında zaten vardı; birinci sınıf sözdizimi olmasa da Object Pascal ve C++’a da kopyalandılar.Alttaki bellek değiştirilebilir olduğu için, gerçekten gerektiğinde ML ailesi dillerde de mutasyonu ele alan mekanizmalar var.
gotoya yakın olsa da,gotoLisp’ten bile daha az popüler.Daha tuhaf olan, modern CPU’larda performans için cache şart olmasına rağmen bellek hiyerarşisini birinci sınıf yurttaş olarak ele alan bir dilin hâlâ olmaması.
Buna en yakın şey, alt çizgilerin deniz gibi taştığı Linux tarzı C gibi görünüyor.
Saf fonksiyonlarla durumu kodlamaya iyi bir giriş sunuyor.
Aslında ağaçlar, tamsayılar, toplam/çarpım tipleri, görüntüler, monad’lar gibi her tür veri için çok daha fazla saf fonksiyonel kodlama var.
Kodlama biraz kafa karıştırıcı olabilir ama aynı zamanda zarif ve küçüktür.
Örneğin JavaScript ile Maybe monad’ını fonksiyonel biçimde uygularsanız şöyle olur:
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 -> rfoldMaybeye geçirilen iki yüksek dereceli fonksiyon sırasıylaNothingveJusta karşılık gelir.Yalnız
Nothingtarafında, biraz daha doğru olmak içinUnitparametresi eklenmiş bir biçim kullanılmıştır.Tip teorisinde oldukça havalıdır ama pratik programlama için pek iyi değildir.
Eskiden gerçek SICP derslerini, yani MIT OCW’nin 1986 kayıtlarını izlemiştim
Bilgi yoğunluğu yüksek diye sık sık övülür ama gerçekte öğrenci soru-cevapları, sınıfta “multimedya” sunum denemelerine eğitmenin dikkat çekmesi için harcanan zaman, tüm ders planı önceden tamamen netleşmediği için soru-cevapları proaktif biçimde engelleyemeyen kısımlar vb. yüzünden epey zaman boşa gider
Tahtaya yazma süresi de birikince hatırı sayılır hale gelir
Elbette materyalin sırası her türlü tartışılıp yeniden düzenlenebilir; bir gün bu içeriği kendi sezgilerime uygun biçimde anlatan bir video serisini bizzat hazırlamayı da planlıyorum
Bu dersin Python gibi daha modern bir dil kullanırken köklerini koruyor gibi görünmesi sevindirici; ayrıca Python pratik bir çok-paradigmalı dil olduğu için, tam bir saflık olmasa da fonksiyonel idiomlar üzerinden sağladığı ifade gücünün insanlar tarafından yeterince takdir edilmediğini düşünüyorum
Python’ın bu dersten çıkarılabileceğini ve çıkarılması gerektiğini düşünüyorum
Python’ın fonksiyonel programlama desteği çok zayıf
Listeler cons tabanlı değil, lambda’lar çok kısıtlı, pattern matching berbat ve ifade tabanlı da değil, ad alanları da garip
Python’a modern bir dil demek de zor; 1990’larda kalmış durumda ve iyi bir C-API’si olduğu için ne yazık ki daha iyi dilleri feda ettirerek büyüdü
Kuyruk çağrısı optimizasyonu olmadığı için bazı kod alıştırmaları Scheme’den tamamen farklı çözümler gerektiriyor
1:1 çevrilmiş kod başarısız olursa eğitmen öğrencilere, seçilen dil yüzünden böyle çalışmadığını söyleyip sadece inanmalarını mı istemeli, yoksa her şeyi yığını dışsallaştırma problemi olarak görüp öyle mi çözmeli, emin değilim
SICP’yi Python’a zorla uydurmaya çalışmak epey aptalca görünüyor
Site şu anda çevrimdışı ama dersleri archive.org’da görmek mümkün
https://en.m.wikipedia.org/wiki/ArsDigita#ArsDigita_Foundati...
https://archive.org/details/arsdigita_01_sicp/
Tüm müfredatı içeren bir USB anahtar satıyorlardı; biri ISO’yu yüklese gerçekten harika olurdu
https://web.archive.org/web/20190222145553/aduni.org/drives/
Scheme’de basitçe ifade edilen şeyler başka dillerde karmaşık alıştırmalara dönüşüyor
Öğrenciler altta yatan kavramlara odaklanmak yerine, Scheme’de umursamak zorunda kalmayacakları uygulama dilinin ayrıntılarına odaklanıyor
cons/car/cdr’nin lambda ile implemente edildiğini ilk gördüğümde sihir gibi gelmiştiAma nihayetinde dil çalışma zamanının bir anahtar/değer sözlüğü implemente ettiğini ve bu implementasyonu ödünç alarak başka veri yapıları oluşturabileceğinizi gösterdiğini düşünüyorum
Elixir’de istediğiniz kadar baştan çekip alabilirsiniz
Davranışı olmayan bir closure, kapanmış değişkenlere giden pointer’dan ibarettir; 2 pointer’a sahip bir closure ise
carvecdrelde edilebilen bir çifttirRuntime, işaret edilen nesnenin tanımın dışından da kullanılabilmesini sağlamalıdır; bu yüzden escape analysis, garbage collection vb. gerekir, ama sözlük gerekmez
Yakınlarda teorem ispatında
0 != 1gibi şeyleri ispatlamak için yalnızca Church encoding’in yetmediği, indüktif veri tipleri gerektiği kavramıyla karşılaştımBununla ilgili şeyleri kabaca buraya koydum; SICP’ye yönelik ayrı bir eleştiri de içinde var: https://intellec7.notion.site/Drinking-SICP-hatorade-and-why...
“Her şey sadece fonksiyondur” bakış açısının sınırlarını daha iyi anlamak istiyorum
0 ≠ 1ispatlanabilir gibi görünüyorf = g -> f x = g xteoreminden çıkarım yapıp sağ tarafta bir eşitsizlik olgusu oluşturabilir ve bunun karşıt tersini alabilirsinizChurch sayıları arasındaki eşitsizliğin, eşitsizliğe dair başka olgular olmadan doğrudan ispatlanamayacağını söylemek doğru görünüyor
Buna karşılık indüktif veri tiplerinde ispat sistemi, aynı indüktif tipin iki somut örneğinde en dıştaki constructor uygulamasını özyinelemeli olarak kaldırarak eşitliği veya eşitsizliği doğrudan “gözlemleyebilir”
Scott encoding veya Church encoding ile bunun başarıp başarılamayacağını merak ediyorum
Kitabın kendisi zaten burada tartışılıyor: https://news.ycombinator.com/item?id=42157558
Bağlantının o sayfanın başlangıcına değil de aşağıdaki tartışmaya gitmesinin bir nedeni var mı, merak ediyorum
Bu yazı mevcut tartışmayla birleştirilemez mi diye düşünüyorum
David Beazley, Python dünyasında oldukça efsanevi bir isim; bu ders ilk başta şaşırtıcı bir fikir gibi görünse de yaklaşık 2 saniye düşününce kusursuz bir eşleşme gibi geldi ve bir sonraki derse kaydoldum.
Esas nokta, bu biçimin gelecekte yazılım mühendisliği sürekli eğitiminin nasıl görüneceğine dair bir örnek olması.
“the substitution model” bölümündeki kodda bir yazım hatası var.
("+", ("fib", ("-", "n", 2)), ("fib", ("-", "n", 1))),Tanımlanan şey
fibonacci;fibtanımlı değil, dolayısıyla ikifibçağrısı kesinliklefibonacciolmalı.Gerçek GitHub deposundaki kod doğru: https://github.com/savarin/pyscheme/blob/0f47292c8e5112425b5...
SICP’nin harika olduğunu düşünüyorum.
Ancak daha çok öğrendikçe ve daha fazla matematik çalıştıkça, ilişkilerin daha temel bir ilkel kavram olduğu sonucuna yaklaştım.
Her fonksiyon, kısıtlı bir ilişki biçimi olarak ifade edilebilir; fakat tersi, ciddi ek mekanizmalar koymadan geçerli değildir.
İlişkisel veritabanları ve SQL, ilişkisel programlamanın en bilinen ve başarılı örnekleri olsa da bu alanın hâlâ büyük ölçüde keşfedilmemiş olduğunu düşünüyorum.
Şu anki ilgim, programlama dili tasarımından çok, çok küçük çocuklara matematiğin temellerini öğretmeye daha yakın.
İlginçtir ki “büyük” gibi yüklemleri tekli ilişki, “daha büyük”ü ikili ilişki olarak öğretmek, aynı kavramı fonksiyon olarak yakalamaya çalışmaktan çok daha kolay.
“Her şey fonksiyondur” bakışını fazla basitleştirilmiş ve çoğu zaman pek yardımcı olmayan bir yaklaşım bulduğum için pek sevmiyorum.
Örneğin cache’e, RAM’e, diske vb. sığmayan fonksiyonlar var; N-way
JOINya da arama/eşleştirme gibi Big-O’su patlayan fonksiyonlar var; idempotent olmayan davranışlar dahil yan etkili fonksiyonlar var.Fonksiyonlara yönelik yan kanal saldırılarını düşünen neredeyse kimse yok.
Tarih, saat, süre vb. şeylere bağlı deterministik olmayan fonksiyonlar da var; ayrıca fonksiyonlar çalışmanın ortasında başarısız olabilir ya da düzgün biçimde başarısız olamayabilir.
Paylaşılan kaynak havuzu kullanan başka “fonksiyonları” etkileyen kaynaklar tüketmediklerini varsaymak da zor.
Fonksiyon argümanları keyfi derecede büyük ya da karmaşık olabilir ama gerçek dünyada sınırlar var; o zaman işaretçilere, sonra da web ya da disk gibi uzak referanslara ihtiyaç duyuluyor.
Ne zaman durmam gerektiğini söylerseniz iyi olur; devam edebilirim.
Bu durumda lambda hesabı modeli, “her şey yalnızca fonksiyondur” yaklaşımının temelidir; çok basit olmasına rağmen Turing makinelerine vb. kıyasla kullanması ve hakkında akıl yürütmesi daha kolay olduğu için iyi bir hesaplama modelidir.
Bu yüzden bilgisayar mantığının ve ispat sistemlerinin çoğunun temelinde yer alır.
Bir fonksiyon bir kaynağa ihtiyaç duyuyorsa o kaynağı talep eder hâle getirilir; tarih/saate bağlıysa tarih/saate bağlı hâle getirilir; deterministik olmayan bir değer döndürüyorsa deterministik olmayan değer döndürür hâle getirilir.
Fonksiyonel programlama yaklaşımının parladığı noktalardan biri, bu tür şeyleri ciddiye almaya zorlamasıdır.
Örtük olarak paylaşılan bir kaynak kullanmak istiyorsanız onu modellemeniz gerekir; örtük paylaşılan kaynağa bağımlı bir “fonksiyon” da gerçek fonksiyondan açıkça ayrılır.
Daha çok prosedür gibiler.
Fonksiyon bir prosedürdür, ama her prosedür fonksiyon değildir.