İçeriğe geç
academia.sh

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ı, apply ile 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat