İçeriğe geç
academia.sh

Ders 17 / 18

Fonksiyonel Programlamaya Giriş

Saf fonksiyonlar, değiştirilemezlik, fonksiyonun değer olarak kullanımı, dönüşüm zincirleri ve yan etki sınırı.

İçindekiler

Önceki ders, nesnelerin durum taşıdığını ve programın çok sayıda değişebilir duruma bölündüğünü belirtti. Bir değişikliğin nereye kadar yayıldığını izlemek, program büyüdükçe güçleşir.

Fonksiyonel programlama bu soruna kökten bir yanıt verir: değerleri hiç değiştirme. Hesaplama, var olan bir şeyi güncellemek değil, girdiden yeni bir değer üretmektir.

Saf Fonksiyon

Bir fonksiyon iki koşulu sağlıyorsa saftır:

  1. Aynı girdi için her zaman aynı sonucu verir.
  2. Gözlenebilir hiçbir yan etkisi yoktur — argümanını değiştirmez, dışarıdaki durumu güncellemez, ekrana yazmaz.
def ortalama(olcumler: list[int]) -> float:      # saf
    return sum(olcumler) / len(olcumler)

toplam_sayac = 0

def kirli_ortalama(olcumler: list[int]) -> float:   # saf değil
    global toplam_sayac
    toplam_sayac += 1                                # dış durum değişti
    olcumler.sort()                                  # argüman değişti
    return sum(olcumler) / len(olcumler)

Saflığın dört pratik sonucu vardır. Fonksiyon, bağlamından bağımsız olarak sınanabilir: girdi verilir, çıktı denetlenir. Sonucu saklanabilir — özyineleme dersindeki not alma tekniği yalnızca saf fonksiyonlarda geçerlidir. Çağrıları birbirinden bağımsız olduğu için farklı iş parçacıklarında paralel çalıştırılabilir. Ve okunurken yalnızca kendisine bakmak yeterlidir; uzaktaki bir durumun ne olduğunu bilmek gerekmez.

Değiştirilemezlik

Saflığın önkoşulu, değerlerin değiştirilmemesidir. Bir listeye eleman eklemek yerine, eski elemanlar artı yenisini içeren yeni bir liste üretilir.

olcumler = [12, 18, 7]

genisletilmis = olcumler + [25]          # yeni liste üretildi
print(olcumler, genisletilmis)           # [12, 18, 7] [12, 18, 7, 25]

sirali = sorted(olcumler)                # yeni liste
print(olcumler, sirali)                  # [12, 18, 7] [7, 12, 18]

İlk bakışta bu, kopya maliyeti demektir. Fonksiyonel dillerde bu maliyet, kalıcı veri yapılarıyla azaltılır: yeni sürüm, eski sürümün değişmeyen parçalarını paylaşır; yalnızca değişen yol kopyalanır. Veri yapıları kursunda bu yapılar ele alınır.

Değiştirilemezliğin kazancı, değişkenler dersindeki paylaşım sorununun ortadan kalkmasıdır. Değiştirilemez bir değeri paylaşmak güvenlidir; kimse onu başkasının altından değiştiremez. Aynı gerekçe, savunma kopyası ihtiyacını da ortadan kaldırır.

Fonksiyonlar Değerdir

Fonksiyonel yaklaşımın ikinci temel fikri, fonksiyonun da bir değer olmasıdır: değişkene bağlanabilir, argüman olarak geçirilebilir, sonuç olarak döndürülebilir. Kapsam dersindeki kapanış örneği bunun ilk uygulamasıydı.

Fonksiyon alan veya döndüren fonksiyonlara yüksek mertebeden fonksiyon denir. Üçü neredeyse her dilde bulunur:

  • Eşleme (map): Her elemanı bir fonksiyondan geçirir, aynı uzunlukta yeni bir dizi üretir.
  • Süzme (filter): Koşulu sağlayan elemanları seçer.
  • İndirgeme (reduce): Diziyi, bir birleştirme fonksiyonuyla tek bir değere indirir.
from functools import reduce

ham = [12, -1, 18, -1, 25, 14]

gecerli = list(filter(lambda o: o >= 0, ham))          # süzme
print(gecerli)                                          # [12, 18, 25, 14]

metrik = list(map(lambda o: o / 100, gecerli))          # eşleme
print(metrik)                                           # [0.12, 0.18, 0.25, 0.14]

toplam = reduce(lambda a, b: a + b, gecerli, 0)         # indirgeme
en_buyuk = reduce(lambda a, b: a if a > b else b, gecerli)

print(toplam, toplam / len(gecerli), en_buyuk)          # 69 17.25 25

Üç işlem, birikeç kalıbının adlandırılmış hâlleridir. Döngü sürümünde “ne yapıldığı” gövdeye gömülüyken, burada işlemin adı doğrudan görünür: süzülüyor mu, dönüştürülüyor mu, toplanıyor mu.

İndirgemenin başlangıç değeri, döngüler dersindeki etkisiz eleman tartışmasının aynısıdır; boş dizide başlangıç değeri verilmezse indirgeme tanımsızdır.

Bileşim

Küçük ve saf fonksiyonlar, çıktısı bir sonrakinin girdisi olacak biçimde zincirlenir. Bu zincire bileşim denir ve fonksiyonel programların temel düzenleme biçimidir.

def gecerli_olanlar(olcumler: list[int]) -> list[int]:
    return [o for o in olcumler if o >= 0]

def ortalama(olcumler: list[int]) -> float:
    return sum(olcumler) / len(olcumler)

def ozet(ham: list[int]) -> tuple[float, int]:
    """Ham veriden ortalama ve en büyük değeri üretir; hiçbir şeyi değiştirmez."""
    gecerli = gecerli_olanlar(ham)
    return ortalama(gecerli), max(gecerli)

ham = [12, -1, 18, -1, 25, 14]
print(ozet(ham))                 # (17.25, 25)
print(ham)                       # [12, -1, 18, -1, 25, 14]  — girdi değişmedi

Nesneye dayalı çözümde veri nesnenin içinde durup mesajlarla değişiyordu; burada veri fonksiyonlar arasında akıyor ve her adım yeni bir değer üretiyor.

Tembel Değerlendirme

Dönüşüm zincirleri, her adımda ara bir koleksiyon üretme eğilimindedir: süzme yeni bir liste, eşleme bir yenisini daha. Girdi büyükse bu ara sonuçlar bellekte gereksiz yer kaplar.

Döngü denetimi dersinde tanıtılan tembel yineleme bu sorunu çözer: değerler istendikçe üretilir, zincirin her adımı tek bir eleman üzerinden akar. Ara liste hiç oluşmaz.

Tembelliğin ikinci sonucu, sonsuz dizilerin tanımlanabilmesidir. Sonu olmayan bir diziden ilk beş eleman alınabilir; alınmayanlar hesaplanmadığı için tanım sonsuz olsa da hesap sonludur. Bazı fonksiyonel diller tembelliği varsayılan davranış yapar; çoğu dilde ise ayrı bir yapı olarak sunulur.

Bedeli, hesabın ne zaman yapıldığının belirsizleşmesidir: zincir kurulduğunda değil, sonuç istendiğinde çalışır. Yan etkili kodla birleştiğinde bu, sırası tahmin edilemeyen davranışa yol açar — tembelliğin saf fonksiyonlarla birlikte anılmasının nedeni budur.

Yan Etkiler Nereye Gider

Hiçbir yan etkisi olmayan program işe yaramaz: sonuç bir yere yazılmalı, girdi bir yerden okunmalıdır. Fonksiyonel yaklaşım yan etkileri yok saymaz, sınıra taşır.

Yaygın düzen şudur: programın çekirdeği saf fonksiyonlardan oluşur, dış kabuk girdi–çıktı işlerini yapar. Kabuk veriyi okur, çekirdeğe verir, dönen sonucu yazar. Böylece iş mantığının tamamı sınanabilir kalır; sınanması güç olan kısım incelir.

Bu düzen, yalnızca fonksiyonel dillerde değil her dilde uygulanabilir. Kurs boyunca yazılan hesaplama fonksiyonlarının hiçbirinin ekrana yazmaması, bu ayrımın bilinçli uygulanmasıydı.

Sınırları

Fonksiyonel yaklaşımın da bedelleri vardır. Doğası gereği durum taşıyan problemler — bir oyunun anlık durumu, bir bağlantının açık olup olmaması — durumu değere çevirmeyi ve her adımda yeni bir sürüm üretmeyi gerektirir; bu, kimi zaman doğal olmayan bir modelleme yaratır.

Başarım da her zaman kendiliğinden gelmez: kalıcı yapıların paylaşımı iyi olsa da, değiştirilebilir bir dizinin yerinde güncellenmesi genellikle daha ucuzdur.

Bu nedenle çoğu dil tek bir paradigmaya bağlı kalmaz. Saf çekirdek ile durumlu kenar, nesnelerle fonksiyonların bir arada kullanılması yaygın pratiktir.

Özet

  • Saf fonksiyon aynı girdi için aynı sonucu verir ve yan etkisi yoktur; sınanabilirlik, not alma ve paralellik bu iki koşuldan doğar.
  • Değiştirilemezlik, değerleri güncellemek yerine yeni değer üretmektir; paylaşımı güvenli kılar ve savunma kopyası ihtiyacını kaldırır.
  • Fonksiyonlar değerdir; eşleme, süzme ve indirgeme birikeç kalıbının adlandırılmış biçimleridir.
  • Bileşim, küçük saf fonksiyonların zincirlenmesidir ve fonksiyonel programın temel düzenleme biçimidir.
  • Yan etkiler yok sayılmaz, programın sınırına taşınır: saf çekirdek, durumlu kabuk.
  • Doğası gereği durumlu problemlerde ve yerinde güncellemenin ucuz olduğu yerlerde yaklaşımın maliyeti vardır.

Sonraki Adım

Üç paradigma da aynı problemi çözdü ve üçü de farklı dillerde farklı ağırlıkta destekleniyor. Kursun son dersi, bir dilin hangi eksenlerde ayrıştığını ve öğrenilecek dilin nasıl seçileceğini ele alacak; ardından bu kursta kurulan kavramların hangi kursla sürdüğünü söyleyecek.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat