---
title: 'Saf Fonksiyonlar'
source: 'https://academia.sh/tr/kurslar/paradigmalar/saf-fonksiyonlar'
course: 'Programlama Paradigmaları'
language: tr
updated: '2026-08-17T18:10:41+00:00'
license: 'CC BY-SA 4.0'
---

# Saf Fonksiyonlar

Yan etkisizlik ve referans saydamlığı: çağrının değeriyle değiştirilmesinin koşturularak sınanması, saf olmayan sürümde davranışın nasıl kaydığı ve iki sürümün testi için gereken düzeneğin satır sayısıyla karşılaştırılması.

Bir önceki ders nesne sorumluluğunu tamamladı: veri ile onu değiştiren davranış aynı
birimde durur, dışarısı duruma değil davranışa seslenir. O konu boyunca birim **nesne**ydi.
Durum vardı, onu koruyan davranış vardı ve ikisi bir arada tutuluyordu.

Bir başka model durumu birimden tamamen çıkarır. Veri üretildikten sonra değiştirilmez,
davranış girdiden çıktıya bir eşlemeye indirgenir, yan etki programın sınırına itilir. Bu
konu o modeli kurar; ilk soru en küçük parçasındadır. Bir fonksiyonun **saf** olması ne
demektir ve saflık, iddia edilmek yerine nasıl koşturulup sınanır?

## Saf Fonksiyonun İki Koşulu

**Saf fonksiyon (pure function)** Programlama Temelleri kursunda iki koşulla tanımlanmıştı:
aynı girdi için her zaman aynı çıktıyı verir ve **yan etki (side effect)** üretmez. Yan
etki, fonksiyonun döndürdüğü değerin dışında kalan her gözlenebilir değişikliktir: kendi
dışındaki bir değişkeni değiştirmek, dosyaya yazmak, ağ isteği açmak, ekrana yazdırmak.

Gönderi ücretlendirme kitaplığının çekirdeği bu tanıma uyar. Ücret, gönderinin ağırlığından
ve bölgesinden, tarifenin kademelerinden ve bölge katsayılarından hesaplanır.

```js
// ucret.mjs — gonderi ucretlendirmenin cekirdegi: girdiden ciktiya bir esleme
export const TARIFE = {
  ad: "yurtici-standart",
  asgariUcret: 4990,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};

export const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1 },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2 },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3 },
];

// Ucret kurus cinsinden tam sayidir; kayan noktali para hesabi yapilmaz.
export function ucretHesapla(gonderi, tarife) {
  const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? tarife.kademeler.at(-1);
  const katsayi = tarife.bolgeKatsayisi[gonderi.bolge];
  return Math.max(tarife.asgariUcret, Math.round(kademe.ucret * katsayi));
}
```

İki koşul da fonksiyonun imzasından okunabilir. Tarife bir parametredir, modül düzeyinde
tutulan bir değer değildir; fonksiyon hiçbir yere yazmaz, yalnızca döndürür.

Aynı hesap, sık rastlanan bir biçimde de yazılabilir. Aşağıdaki sürümde tarife dışarıdan
alınmaz, aylık hacim modül düzeyinde biriktirilir ve her çağrı bir kayıt satırı üretir.

```js
// kirli-ucret.mjs — ayni hesap, ama aylik hacim sayaci modul duzeyinde tutuluyor
import { TARIFE } from "./ucret.mjs";

const HACIM_ESIGI = 20000;
let aylikHacim = 0;

export function ucretHesaplaKirli(gonderi) {
  const kademe = TARIFE.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? TARIFE.kademeler.at(-1);
  const temel = Math.max(TARIFE.asgariUcret,
    Math.round(kademe.ucret * TARIFE.bolgeKatsayisi[gonderi.bolge]));
  aylikHacim += temel;
  console.log(`[kayit] ${gonderi.kod} ucretlendirildi`);
  return Math.round(temel * (aylikHacim > HACIM_ESIGI ? 0.9 : 1));
}

export const sifirla = () => { aylikHacim = 0; };
```

İkinci sürüm de doğru sonuçlar üretir. Hacim indirimi gerçek bir iş kuralıdır, kayıt satırı
gerçek bir gereksinimdir. Aradaki fark davranışın doğruluğunda değil, davranışın nereye
bağlı olduğundadır: `ucretHesaplaKirli` çağrısının sonucu, kendisinden önce kaç çağrı
yapıldığına bağlıdır.

## Referans Saydamlığı

Bu bağlılığın ölçülebilir bir sınavı vardır. **Referans saydamlığı (referential
transparency)**, bir ifadenin, ürettiği değerle değiştirilmesi durumunda programın
davranışının değişmemesidir. Sınav yordamı doğrudan bu tanımdan çıkar: aynı çağrı üç kez
yazılır, sonra çağrı bir kez hesaplanıp sonucu üç yere gömülür, iki koşumun sonuçları
karşılaştırılır.

```js
// saydamlik.mjs — cagriyi degeriyle degistirmek davranisi degistiriyor mu
import { TARIFE, GONDERILER, ucretHesapla } from "./ucret.mjs";
import { ucretHesaplaKirli, sifirla } from "./kirli-ucret.mjs";

const g = GONDERILER[1];

const cagriyla = [ucretHesapla(g, TARIFE), ucretHesapla(g, TARIFE), ucretHesapla(g, TARIFE)];
const deger = ucretHesapla(g, TARIFE);
const gomulu = [deger, deger, deger];
console.log(`saf   / cagriyla : ${cagriyla.join(", ")}`);
console.log(`saf   / gomulu   : ${gomulu.join(", ")}`);
console.log(`saf   / esit mi  : ${cagriyla.join() === gomulu.join()}`);

sifirla();
const kCagriyla = [ucretHesaplaKirli(g), ucretHesaplaKirli(g), ucretHesaplaKirli(g)];
sifirla();
const kDeger = ucretHesaplaKirli(g);
const kGomulu = [kDeger, kDeger, kDeger];
console.log(`kirli / cagriyla : ${kCagriyla.join(", ")}`);
console.log(`kirli / gomulu   : ${kGomulu.join(", ")}`);
console.log(`kirli / esit mi  : ${kCagriyla.join() === kGomulu.join()}`);
```

```
saf   / cagriyla : 7463, 7463, 7463
saf   / gomulu   : 7463, 7463, 7463
saf   / esit mi  : true
[kayit] TR-4472 ucretlendirildi
[kayit] TR-4472 ucretlendirildi
[kayit] TR-4472 ucretlendirildi
[kayit] TR-4472 ucretlendirildi
kirli / cagriyla : 7463, 7463, 6717
kirli / gomulu   : 7463, 7463, 7463
kirli / esit mi  : false
```

Çıktıda iki ayrı sapma vardır. Birincisi döndürülen değerlerdedir: saf olmayan sürümde
üçüncü çağrı, biriken hacim eşiği aştığı için 7463 yerine 6717 döndürür. Değişiklik
gönderide değil, çağrı geçmişindedir. İkincisi kayıt satırlarındadır. Toplam dört satır
basılmıştır: üç çağrılı koşumdan üç, tek çağrılı koşumdan bir. İfadeyi değeriyle
değiştirmek yalnızca sonucu değil, programın ürettiği kayıt sayısını da değiştirmiştir.

Referans saydamlığının pratikteki karşılığı üç maddedir. Bir çağrının sonucu önbelleğe
alınabilir, çünkü sonraki çağrılar aynı değeri verir. Çağrılar yeniden sıralanabilir ya da
gereksizse hiç yapılmayabilir, çünkü sıra sonucu etkilemez. Bir hata raporundaki girdi
yeterlidir; hatayı yeniden üretmek için çağrı geçmişini kurmak gerekmez. Saf olmayan
sürümde bu üç maddenin hiçbiri geçerli değildir.

## Saflığın Testte Görünen Bedeli

Aradaki fark en somut biçimde testte görünür. İki sürüm için aynı üç iddia yazılır: üç
gönderinin ücreti sırasıyla 4990, 7463 ve 14715 olmalıdır.

```js
// saf.test.mjs — uc gonderi, uc iddia; kurulum yalnizca girdinin kendisi
import test from "node:test";
import assert from "node:assert/strict";
import { TARIFE, GONDERILER, ucretHesapla } from "./ucret.mjs";

test("ucret kademe ve bolge katsayisiyla hesaplanir", () => {
  assert.equal(ucretHesapla(GONDERILER[0], TARIFE), 4990);
  assert.equal(ucretHesapla(GONDERILER[1], TARIFE), 7463);
  assert.equal(ucretHesapla(GONDERILER[2], TARIFE), 14715);
});
```

```js
// kirli.test.mjs — ayni uc iddia; sayaci ve kayit akisini testin denetlemesi gerekiyor
import test, { beforeEach, afterEach } from "node:test";
import assert from "node:assert/strict";
import { GONDERILER } from "./ucret.mjs";
import { ucretHesaplaKirli, sifirla } from "./kirli-ucret.mjs";

const gercekLog = console.log;

beforeEach(() => {
  sifirla();
  console.log = () => {};
});

afterEach(() => {
  console.log = gercekLog;
});

test("bir kilograma kadar, birinci bolge", () => {
  assert.equal(ucretHesaplaKirli(GONDERILER[0]), 4990);
});

test("bes kilograma kadar, ikinci bolge", () => {
  assert.equal(ucretHesaplaKirli(GONDERILER[1]), 7463);
});

test("on bes kilograma kadar, ucuncu bolge", () => {
  assert.equal(ucretHesaplaKirli(GONDERILER[2]), 14715);
});
```

```sh
node --test --test-reporter=tap 2>&1 | grep -E '^# (tests|pass|fail)'
for f in saf.test.mjs kirli.test.mjs; do
  printf '%-15s %2d satir\n' "$f" "$(awk 'END{print NR}' "$f")"
done
```

```
# tests 4
# pass 4
# fail 0
saf.test.mjs    10 satir
kirli.test.mjs  28 satir
```

Her iki dosya da geçer, ama aynı üç iddia bir dosyada 10, diğerinde 28 satır tutar. Fazlalık
iddialarda değil **düzenekte (fixture)** birikir. Saf olmayan sürümün testi üç şeyi
yapmak zorundadır: her denemeden önce modül düzeyindeki sayacı sıfırlamak, kayıt akışını
susturup sonra geri koymak ve üç iddiayı ayrı denemelere bölmek. Üçüncüsü isteğe bağlı
değildir; iddialar tek gövdede kalırsa biriken hacim üçüncü iddiayı düşürür.

Bu satırların hepsi, üretimde bulunmayan bir düzeni testte kurmak içindir. Saf sürümde
kurulacak bir düzen yoktur, çünkü fonksiyonun ihtiyacı olan her şey parametrelerindedir.

## Saflığın Sınırı

Saflık, fonksiyonun içinde hiçbir şeyin değişmemesi demek değildir. Ölçüt
**gözlenebilirliktir**: fonksiyonun gövdesinde tanımlanmış bir değişken kaç kez
değiştirilirse değiştirilsin, dışarıdan görülmediği sürece fonksiyon saftır. Aşağıdaki
döngü yerel bir birikeç kullanır ve saflığı bozmaz.

```js
// toplam.mjs — yerel degisken degisir, disaridan gorunmez: fonksiyon saf kalir
export function toplamUcret(gonderiler, ucretlendir) {
  let toplam = 0;
  for (const g of gonderiler) toplam += ucretlendir(g);
  return toplam;
}
```

İki sınır ayrıca not edilmelidir. Birincisi, saf bir fonksiyon parametre olarak aldığı
nesneyi değiştirmemelidir; değiştirirse çağıranın gördüğü veri de değişir ve bu bir yan
etkidir. Bu kısıtı sağlamanın yolu bir sonraki dersin konusudur. İkincisi, zaman ve
rastgelelik saflığın doğal düşmanlarıdır: `Date.now` çağrısını gövdeye koymak, aynı girdiye
aynı çıktı koşulunu kırar. Çözüm bu değerleri yasaklamak değil, dışarıda üretip parametre
olarak geçirmektir. Kural, ücretlendirme kitaplığının tamamı için tek cümlede yazılabilir:
çekirdek yalnızca kendisine verileni okur.

## Özet

- Saf fonksiyon iki koşulu sağlar: aynı girdiye aynı çıktı ve yan etki üretmemek. Koşullar
  fonksiyonun imzasından okunur; tarife parametredir, modül değişkeni değildir.
- Referans saydamlığı, bir çağrının değeriyle değiştirilmesinin davranışı değiştirmemesidir.
  Sınav koşturularak yapılır: saf sürümde iki koşum eşit, saf olmayan sürümde üçüncü değer
  7463 yerine 6717 ve kayıt satırı sayısı üç yerine bir çıkar.
- Saf olmayan sürümde önbellekleme, çağrıları yeniden sıralama ve hatayı yalnızca girdiden
  yeniden üretme olanaklarının üçü de kaybolur.
- Aynı üç iddia saf sürümde 10, saf olmayan sürümde 28 satır tutar; fark iddialarda değil,
  sayacı sıfırlayan ve kayıt akışını susturan düzenektedir.
- Fonksiyonun gövdesinde tanımlanmış bir değişkenin değişmesi saflığı bozmaz; ölçüt
  değişikliğin dışarıdan gözlenip gözlenmediğidir.

## Sonraki Adım

Bu dersin saf fonksiyonu kendi parametresini değiştirmedi, ama bunu bir kural olarak
uygulayan hiçbir şey yoktu. Tarife nesnesi çağıranla paylaşılmış tek bir nesnedir; onu
değiştiren tek bir satır, birbirinden habersiz iki çağrının sonucunu birbirine bağlar.
Sonraki ders paylaşılan bu veriyi ele alıyor: aynı tarifeyi kullanan iki koşum yolunda iç
içe yapının kopyalanmaması kaç sessiz değişikliğe yol açar, kopyalamanın bedeli kaç nesnedir
ve bu bedel nerede ödenmeyebilir.
