Ders 11 / 17
Yüksek Mertebeden Fonksiyonlar
Fonksiyonun değer olarak kullanılması, eşleme–süzme–indirgeme zinciri, karşılaştırıcı üreten fonksiyonlar, bileşim ve davranış ekleyen sarmalayıcılar.
İçindekiler
Önceki dersler fonksiyonu sürekli değer gibi kullandı: geri çağrı olarak aktarıldı, bir
fabrikadan döndürüldü, nesneye özellik olarak yazıldı, bind ile yenisi üretildi.
Hepsinin dayanağı tek bir olgudur — JavaScript’te fonksiyon bir nesnedir; çağrılabilir
olması dışında sayı ya da dizgiden farkı yoktur.
Fonksiyonu argüman olarak alan ya da fonksiyon döndüren fonksiyonlara yüksek mertebeden fonksiyon (higher-order function) denir. Programlama Temelleri kursundaki Fonksiyonel Programlamaya Giriş dersi kavramı tanıtmıştı. Bu ders onu ölçüm kayıtları üzerinde uygular ve önceki dersteki bağlanma kurallarının bu kullanımda nereye değdiğini gösterir.
Fonksiyonu Argüman Olarak Vermek
En yaygın biçim, bir koleksiyon üzerindeki işlemin ne yapacağını dışarıdan almasıdır. Süzme ölçütü, dönüştürme kuralı ve birikeç işlemi ayrı ayrı adlandırılmış fonksiyonlarda tutulabilir.
const kayitlar = [ { sensor: "S-01", deger: 21.4, zaman: 1000 }, { sensor: "S-02", deger: 19.8, zaman: 1060 }, { sensor: "S-01", deger: 25.1, zaman: 1120 }, { sensor: "S-03", deger: 18.2, zaman: 1180 }, { sensor: "S-01", deger: 22.0, zaman: 1240 }, ]; const esikUstunde = (kayit) => kayit.deger > 20; const degeriAl = (kayit) => kayit.deger; const topla = (birikec, deger) => birikec + deger; const secilenler = kayitlar.filter(esikUstunde); const degerler = secilenler.map(degeriAl); const toplam = degerler.reduce(topla, 0); console.log(secilenler.length); console.log(degerler.join(",")); console.log(toplam.toFixed(1)); console.log((toplam / degerler.length).toFixed(3)); const sensoreGoreGrup = kayitlar.reduce((grup, kayit) => { (grup[kayit.sensor] ??= []).push(kayit.deger); return grup; }, Object.create(null)); console.log(Object.keys(sensoreGoreGrup).join(",")); console.log(sensoreGoreGrup["S-01"].join(",")); console.log(typeof esikUstunde); console.log(esikUstunde.length);
3 21.4,25.1,22 68.5 22.833 S-01,S-02,S-03 21.4,25.1,22 function 1
Süzme, eşleme ve indirgeme üç ayrı sorumluluğu ayırır: hangi kayıtların ilgilendiği, o kayıtlardan hangi bilginin alındığı ve bilginin nasıl birleştirildiği. Her biri ayrı ayrı sınanabilir ve başka bağlamlarda yeniden kullanılabilir.
Gruplama örneği indirgemenin genelliğini gösterir: birikeç bir sayı olmak zorunda değildir,
bir nesne de olabilir. Birikecin Object.create(null) ile kurulması bilinçlidir — sensör
adları veri kaynağından geldiği için, zincirden gelen adlarla çakışma riski böylece
ortadan kalkar.
Son iki satır fonksiyonun sıradan bir değer olduğunu doğrular: türü sorgulanabilir, parametre sayısı okunabilir, bir değişkende saklanabilir.
Fonksiyon Döndüren Fonksiyonlar
İkinci biçim, bir fonksiyonu üreten fonksiyondur. Üretilen fonksiyon, üreticinin parametrelerini kapanışla taşır; böylece davranışı dışarıdan ayarlanmış özel bir işlev elde edilir.
function anahtaraGore(anahtarIslevi, ters = false) { const yon = ters ? -1 : 1; return (a, b) => { const solAnahtar = anahtarIslevi(a); const sagAnahtar = anahtarIslevi(b); if (solAnahtar < sagAnahtar) return -1 * yon; if (solAnahtar > sagAnahtar) return 1 * yon; return 0; }; } const kayitlar = [ { sensor: "S-02", deger: 19.8 }, { sensor: "S-01", deger: 25.1 }, { sensor: "S-03", deger: 18.2 }, { sensor: "S-01", deger: 21.4 }, ]; const bicim = (liste) => liste.map((k) => `${k.sensor}:${k.deger}`).join(" "); console.log(bicim([...kayitlar].sort(anahtaraGore((k) => k.deger)))); console.log(bicim([...kayitlar].sort(anahtaraGore((k) => k.deger, true)))); console.log(bicim([...kayitlar].sort(anahtaraGore((k) => k.sensor)))); console.log(bicim(kayitlar));
S-03:18.2 S-02:19.8 S-01:21.4 S-01:25.1 S-01:25.1 S-01:21.4 S-02:19.8 S-03:18.2 S-01:25.1 S-01:21.4 S-02:19.8 S-03:18.2 S-02:19.8 S-01:25.1 S-03:18.2 S-01:21.4
Tek bir anahtaraGore işlevi, üç farklı sıralama ölçütü üretir. Karşılaştırma mantığı bir
kez yazılır; değişen tek şey anahtarın nasıl çıkarıldığıdır. Bu, Algoritmalar kursundaki
Sıralama Algoritması Seçimi dersinde geçen bileşik anahtar fikrinin fonksiyon düzeyindeki
karşılığıdır.
Üçüncü satır sıralamanın kararlılığını gösterir: sensör adına göre sıralandığında iki
S-01 kaydı, girdideki göreli sıralarını korur. Son satır ise kaynağın değişmediğini
doğrular — sıralama yerinde çalıştığı için her çağrıda yayma ile kopya alınmıştır.
Bileşim
Fonksiyon döndürmenin ikinci kullanımı, birden çok dönüşümü tek bir işleve bileştirmektir. Her adım kendi başına yazılır, sıra bir kez kurulur.
function kalibratorUret(kayma, carpan) { return (hamDeger) => (hamDeger + kayma) * carpan; } function birlestir(...islevler) { return (baslangic) => islevler.reduce((deger, islev) => islev(deger), baslangic); } const sensorS01 = kalibratorUret(-0.4, 1.02); const sensorS02 = kalibratorUret(0.15, 1.0); console.log(sensorS01(21.4).toFixed(3)); console.log(sensorS02(19.8).toFixed(3)); const yuvarla = (deger) => Math.round(deger * 10) / 10; const santigrattanFahrenhayta = (deger) => deger * 1.8 + 32; const boruHatti = birlestir(sensorS01, yuvarla, santigrattanFahrenhayta, yuvarla); console.log(boruHatti(21.4)); console.log(boruHatti(19.8)); console.log(birlestir()(5));
21.420 19.950 70.5 67.6 5
birlestir işlevinin kendisi de bir indirgemedir: başlangıç değeri girdi, birikeç işlemi
“sıradaki işlevi uygula”. Argümansız çağrıldığında birim işlevi üretir — hiçbir dönüşüm
uygulanmaz — ve bu, bileşimin doğru tanımlandığının göstergesidir.
kalibratorUret ile bind ile yapılan kısmi uygulama aynı sonucu verir. Fark okunurlukta
ve bağlamdadır: kapanışlı biçim this kullanmaz, hangi parametrenin sabitlendiği adından
bellidir ve bağlanma kurallarının hiçbirine tabi değildir.
Yöntemi Değer Olarak Aktarmak
Yüksek mertebeden bir fonksiyona bir yöntem verildiğinde, önceki derslerdeki kopma sorunu geri döner: aktarılan şey fonksiyondur, bulunduğu nesne değil.
class OlcumKaydi { constructor(sensor, deger) { this.sensor = sensor; this.deger = deger; } etiket() { return `${this.sensor}:${this.deger}`; } } const kayitlar = [new OlcumKaydi("S-01", 21.4), new OlcumKaydi("S-02", 19.8)]; try { console.log(kayitlar.map(OlcumKaydi.prototype.etiket).join(" ")); } catch (hata) { console.log(`dogrudan yontem: ${hata.constructor.name}`); } console.log(kayitlar.map((kayit) => kayit.etiket()).join(" ")); console.log(kayitlar.map(Function.prototype.call, OlcumKaydi.prototype.etiket).join(" ")); function uygulaHepsine(liste, islev, baglam) { const sonuc = []; for (const oge of liste) sonuc.push(islev.call(baglam, oge)); return sonuc; } console.log(uygulaHepsine([1, 2, 3], function (sayi) { return sayi * this.carpan; }, { carpan: 10 }).join(","));
dogrudan yontem: TypeError S-01:21.4 S-02:19.8 S-01:21.4 S-02:19.8 10,20,30
Prototipteki yöntem doğrudan verildiğinde this bağlanmaz. En okunur çözüm çağrıyı bir ok
fonksiyonuyla sarmaktır. Üçüncü satır ise map işlevinin bağlam parametresini kullanan
daha dolaylı bir yoldur: eşlenen fonksiyon call olur, bağlam da asıl yöntem; her öge
call işleminin ilk argümanı olarak aktarılır. Yazımı kısa, okunması zordur; kısalık
uğruna okunurluktan vazgeçmenin iyi bir örneğidir.
Son bölüm, kendi yazdığınız yüksek mertebeden fonksiyonlarda bağlam desteğinin nasıl
verileceğini gösterir: islev.call(baglam, oge) biçimi, yerleşik dizi yöntemlerinin
ikinci parametresiyle aynı sözleşmeyi kurar.
Davranış Ekleyen Sarmalayıcılar
Üçüncü biçim, bir fonksiyonu alıp aynı arayüzü sunan ama davranışı genişletilmiş bir fonksiyon döndürmektir. Kapanışlar dersindeki not alma sarmalayıcısı bunun bir örneğiydi; kalıp geneldir.
function birKere(islev) { let calisti = false; let sonuc; return function (...argumanlar) { if (!calisti) { calisti = true; sonuc = islev.apply(this, argumanlar); } return sonuc; }; } function dogrulayarakSar(islev, gecerliMi, ileti) { return function (...argumanlar) { if (!argumanlar.every(gecerliMi)) throw new RangeError(ileti); return islev.apply(this, argumanlar); }; } let kurulumSayisi = 0; const kurulumYap = birKere((sensor) => { kurulumSayisi += 1; return `${sensor} kuruldu`; }); console.log(kurulumYap("S-01")); console.log(kurulumYap("S-02")); console.log(kurulumSayisi); const guvenliOrtalama = dogrulayarakSar( (...degerler) => degerler.reduce((t, d) => t + d, 0) / degerler.length, (deger) => typeof deger === "number" && Number.isFinite(deger), "tum degerler sonlu sayi olmalidir", ); console.log(guvenliOrtalama(21.4, 19.8, 25.1).toFixed(3)); try { guvenliOrtalama(21.4, "yirmi"); } catch (hata) { console.log(`${hata.constructor.name}: ${hata.message}`); } const kayit = { sensor: "S-01", ozet: dogrulayarakSar( function (onEk) { return `${onEk}-${this.sensor}`; }, (a) => typeof a === "string", "on ek dizgi olmalidir", ), }; console.log(kayit.ozet("olcum"));
S-01 kuruldu S-01 kuruldu 1 22.100 RangeError: tum degerler sonlu sayi olmalidir olcum-S-01
İki sarmalayıcı da üç kurala uyar. Aynı parametreleri kabul ederler — bu yüzden kalan
parametreler kullanılır. Aynı dönüş değerini verirler. Ve bağlamı korurlar: gövdeleri ok
fonksiyonu değil işlev ifadesidir, apply çağrısında this aktarılır. Son bölüm bu
üçüncü kuralın sonucudur — sarmalanmış fonksiyon bir nesnenin yöntemi olarak
çağrıldığında this doğru nesneye bağlanır.
Sarmalayıcı ok fonksiyonuyla yazılsaydı this sarmalayıcının tanımlandığı kapsamdan
gelirdi ve yöntem olarak kullanılamazdı. Ok fonksiyonlarının sınırı burada da geçerlidir.
Özet
- Fonksiyon bir değerdir: değişkende saklanır, argüman olarak verilir, döndürülür ve özellik taşır.
- Süzme, eşleme ve indirgeme, bir koleksiyon üzerindeki işlemin ölçütünü dışarıdan alarak sorumlulukları ayırır; indirgemenin birikeci nesne de olabilir.
- Fonksiyon döndüren fonksiyonlar, parametreleri kapanışla taşıyan özelleşmiş işlevler üretir; sıralama ölçütleri ve kalibratörler bunun tipik örnekleridir.
- Bileşim, tek tek yazılmış dönüşümleri sırayla uygulayan bir indirgemedir.
- Yöntemi doğrudan yüksek mertebeden bir fonksiyona vermek bağı koparır; çağrıyı saran bir ok fonksiyonu en okunur çözümdür.
- Sarmalayıcılar aynı parametreleri ve dönüş değerini korumalı,
applyile bağlamı aktarmalıdır; bu yüzden ok fonksiyonu olarak yazılamazlar.
Sonraki Adım
Bu derste gruplama yapılırken birikeç Object.create(null) ile kuruldu; nedeni, sensör
adlarının zincirden gelen adlarla çakışabilmesiydi. Aynı önlem kursun ilk dersinde de,
kapanışlardaki önbellekte de gerekti. Nesneyi sözlük olarak kullanmanın bu ve başka
sınırları vardır: anahtarlar dizgiye çevrilir, sıra garantisi kısıtlıdır, eleman sayısı
doğrudan okunamaz. Sonraki konu, dilin bu iş için ayrılmış türlerini — anahtarlı ve tekil
koleksiyonları — tanıtarak başlayacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.