← Kursa Dön
📄 Text · 15 min

Fonksiyonel Programlama Desenleri

Giriş: Farklı Bir Bakış Açısı

Şimdiye kadar çoğunlukla nesne yönelimli (OOP) düşündük: sınıflar, nesneler, methodlar. Ama programlamanın başka bir felsefesi daha var: Fonksiyonel Programlama (FP).

OOP'ta "nesneler ne yapabilir?" diye sorarsın. FP'de ise "veriye ne dönüşümler uygulayacağım?" diye sorarsın. Birinde durum (state) yönetirsin, diğerinde dönüşüm (transformation) zincirleri kurarsın.

Python saf bir fonksiyonel dil değil — ama fonksiyonel araçları güçlü. Bu derste FP'nin temel kavramlarını ve Python'daki araçlarını öğreneceksin.


Fonksiyonel Programlama Nedir?

Analoji: Matematik Fonksiyonu

Matematikte bir fonksiyon düşün: f(x) = x². Bu fonksiyonu kaç kez çağırırsan çağır, aynı x için hep aynı sonucu verir. Hiçbir "dışarıdaki değişkeni" değiştirmez. Saf, temiz, öngörülebilir.

Fonksiyonel programlama da bu felsefeye dayanır:

  1. Pure functions: Aynı girdi → hep aynı çıktı. Yan etkisi yok.

  2. Immutability: Veriyi değiştirmek yerine yeni veri oluştur.

  3. First-class functions: Fonksiyonlar da birer değer — değişkene ata, parametre olarak geçir, fonksiyondan döndür.

  4. Declarative style: "Nasıl yapılacağı" yerine "ne yapılacağı" tanımlanır.


Pure Functions: Aynı Input → Aynı Output

Pure function'ın iki kuralı var:

  1. Aynı argümanlarla çağrıldığında her zaman aynı sonucu döner

  2. Yan etkisi (side effect) yoktur — dış dünyayı değiştirmez

Pure Function Örnekleri

# ✅ Pure: Aynı input → aynı output, yan etkisi yok
def add(a, b):
    return a + b

def square(x):
    return x ** 2

def format_name(first, last):
    return f"{first} {last}"

# Her zaman aynı sonuç
print(add(3, 5))           # 8 (her seferinde)
print(square(4))           # 16 (her seferinde)
print(format_name("Ali", "Yılmaz"))  # Ali Yılmaz (her seferinde)

Impure Function Örnekleri

# ❌ Impure: Dış duruma bağlı veya değiştiriyor
total = 0

def add_to_total(x):
    global total
    total += x    # Dış durumu değiştiriyor (side effect)
    return total  # Sonuç dış duruma bağlı

print(add_to_total(5))   # 5
print(add_to_total(5))   # 10 — Aynı input, farklı output!

# ❌ Impure: print() bir side effect
def greet(name):
    print(f"Merhaba {name}")  # Ekrana yazma = side effect
    return name

# ❌ Impure: Dış duruma bağlı
import random
def roll_dice():
    return random.randint(1, 6)  # Her seferinde farklı sonuç

Neden Pure Fonksiyonlar Önemli?

  • Test etmesi kolay: Belirli input → beklenen output. Mock'a gerek yok.

  • Debug'ı kolay: Yan etkisi olmadığından hata izlemek basit.

  • Paralel çalışmaya uygun: Paylaşılan durum yok, race condition yok.

  • Cache'lenebilir: Aynı input hep aynı output → sonucu cache'le.

  • Güvenilir: Sürpriz davranış yok.


First-Class Functions: Fonksiyon = Değer

Python'da fonksiyonlar birinci sınıf vatandaş (first-class citizen). Yani fonksiyonlar da tıpkı sayılar veya stringler gibi birer değer.

# Fonksiyonu değişkene ata
def greet(name):
    return f"Merhaba {name}!"

selamla = greet  # Parantez yok! Fonksiyonun kendisini atıyoruz
print(selamla("Ali"))  # Merhaba Ali!

# Fonksiyonu listeye koy
def square(x): return x ** 2
def cube(x): return x ** 3
def negate(x): return -x

operations = [square, cube, negate]
for op in operations:
    print(f"{op.__name__}(5) = {op(5)}")
# square(5) = 25
# cube(5) = 125
# negate(5) = -5

# Fonksiyonu dict'te sakla
dispatch = {
    "add": lambda a, b: a + b,
    "sub": lambda a, b: a - b,
    "mul": lambda a, b: a * b,
}
print(dispatch["add"](3, 5))  # 8

Higher-Order Functions

Higher-order function, fonksiyon alan veya fonksiyon döndüren fonksiyondur.

Fonksiyon Alan Fonksiyonlar

def apply_operation(func, value):
    """Verilen fonksiyonu verilen değere uygula."""
    return func(value)

print(apply_operation(abs, -5))      # 5
print(apply_operation(str, 42))      # "42"
print(apply_operation(len, "hello")) # 5

# Lambda ile
print(apply_operation(lambda x: x ** 2, 7))  # 49

Fonksiyon Döndüren Fonksiyonlar

def multiplier(factor):
    """Verilen çarpan ile çarpan bir fonksiyon döndürür."""
    def multiply(x):
        return x * factor
    return multiply

double = multiplier(2)
triple = multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15

# Closure! inner fonksiyon, outer fonksiyonun değişkenini hatırlar

Gerçek Dünya Örneği: Validator Fabrikası

def make_validator(min_val, max_val):
    """Belirli aralıkta doğrulama yapan fonksiyon üretir."""
    def validate(value):
        if min_val <= value <= max_val:
            return True
        raise ValueError(f"{value}, {min_val}-{max_val} aralığında olmalı")
    return validate

validate_age = make_validator(0, 150)
validate_score = make_validator(0, 100)
validate_percentage = make_validator(0.0, 1.0)

print(validate_age(25))     # True
print(validate_score(85))   # True
# validate_age(200)         # ValueError: 200, 0-150 aralığında olmalı

Immutability Tercihi

Fonksiyonel programlamada veriyi değiştirmek yerine yeni kopya oluşturursun.

# ❌ Mutable yaklaşım: Listeyi yerinde değiştir
def add_item_mutable(items, item):
    items.append(item)  # Orijinal listeyi değiştiriyor!
    return items

original = [1, 2, 3]
result = add_item_mutable(original, 4)
print(original)  # [1, 2, 3, 4] — Orijinal değişti!

# ✅ Immutable yaklaşım: Yeni liste oluştur
def add_item_immutable(items, item):
    return [*items, item]  # Yeni liste döner

original = [1, 2, 3]
result = add_item_immutable(original, 4)
print(original)  # [1, 2, 3] — Orijinal korundu!
print(result)    # [1, 2, 3, 4]

Immutable Veri Yapıları

# Tuple: immutable liste
point = (3, 4)
# point[0] = 5  # TypeError!

# frozenset: immutable set
colors = frozenset({"red", "green", "blue"})
# colors.add("yellow")  # AttributeError!

# Named tuple: immutable ve okunaklı
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
# p.x = 5  # AttributeError!
p2 = p._replace(x=5)  # Yeni nesne oluştur
print(p2)  # Point(x=5, y=4)

map, filter, reduce: Dönüşüm Üçlüsü

Bu üç fonksiyon FP'nin temel taşları.

map(): Dönüştür

Her elemana bir fonksiyon uygular:

numbers = [1, 2, 3, 4, 5]

# map ile
squares = list(map(lambda x: x ** 2, numbers))
print(squares)  # [1, 4, 9, 16, 25]

# List comprehension eşdeğeri
squares = [x ** 2 for x in numbers]

# Birden fazla iterable ile
names = ["ali", "veli", "ayşe"]
lengths = list(map(len, names))
print(lengths)  # [3, 4, 4]

# Birden fazla liste ile map
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
print(sums)  # [11, 22, 33]

filter(): Filtrele

Koşulu sağlayan elemanları seç:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Çift sayıları filtrele
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4, 6, 8, 10]

# List comprehension eşdeğeri
evens = [x for x in numbers if x % 2 == 0]

# None ile filter: falsy değerleri atar
mixed = [0, "", "hello", None, 42, [], "world", False]
truthy = list(filter(None, mixed))
print(truthy)  # ['hello', 42, 'world']

reduce(): İndirge

Tüm elemanları tek bir değere indirger:

from functools import reduce

numbers = [1, 2, 3, 4, 5]

# Toplam
total = reduce(lambda acc, x: acc + x, numbers)
print(total)  # 15

# Çarpım
product = reduce(lambda acc, x: acc * x, numbers)
print(product)  # 120

# Başlangıç değeri ile
total = reduce(lambda acc, x: acc + x, numbers, 100)
print(total)  # 115

# En büyük sayı
maximum = reduce(lambda a, b: a if a > b else b, numbers)
print(maximum)  # 5

Zincirleme Kullanım

from functools import reduce

# Pipeline: sayıları al → çiftleri filtrele → karelerini al → topla
numbers = range(1, 11)

result = reduce(
    lambda acc, x: acc + x,
    map(
        lambda x: x ** 2,
        filter(
            lambda x: x % 2 == 0,
            numbers
        )
    )
)
print(result)  # 220 (4 + 16 + 36 + 64 + 100)

# List comprehension ile daha okunaklı:
result = sum(x ** 2 for x in range(1, 11) if x % 2 == 0)
print(result)  # 220

💡 İpucu: Python'da map/filter yerine genellikle list comprehension veya generator expression tercih edilir — daha okunaklıdır. Ama FP kavramlarını anlamak önemli çünkü başka dillerde (Haskell, Scala, Rust) çok kullanılır.


Function Composition: f(g(x))

Matematikte (f ∘ g)(x) = f(g(x)). Programlamada da fonksiyonları birleştirebiliriz.

def compose(*funcs):
    """Birden fazla fonksiyonu birleştir: compose(f, g, h)(x) = f(g(h(x)))"""
    def composed(x):
        result = x
        for f in reversed(funcs):
            result = f(result)
        return result
    return composed

# Fonksiyonları tanımla
def double(x): return x * 2
def add_one(x): return x + 1
def square(x): return x ** 2

# Birleştir
transform = compose(square, add_one, double)
# double(5) = 10 → add_one(10) = 11 → square(11) = 121
print(transform(5))  # 121

Pipe: Soldan Sağa Composition

def pipe(*funcs):
    """Soldan sağa fonksiyon zinciri: pipe(f, g, h)(x) = h(g(f(x)))"""
    def piped(x):
        result = x
        for f in funcs:
            result = f(result)
        return result
    return piped

# Veri işleme pipeline'ı
process = pipe(
    str.strip,           # Boşlukları temizle
    str.lower,           # Küçük harfe çevir
    lambda s: s.replace(" ", "_"),  # Boşlukları _ yap
)

print(process("  Hello World  "))  # hello_world

Gerçek Dünya: Veri Temizleme Pipeline'ı

def remove_nulls(data):
    """None değerleri kaldır."""
    return [x for x in data if x is not None]

def convert_to_float(data):
    """String'leri float'a çevir."""
    results = []
    for x in data:
        try:
            results.append(float(x))
        except (ValueError, TypeError):
            pass
    return results

def remove_outliers(data, min_val=0, max_val=1000):
    """Aykırı değerleri kaldır."""
    return [x for x in data if min_val <= x <= max_val]

def calculate_stats(data):
    """İstatistik hesapla."""
    if not data:
        return {}
    return {
        "count": len(data),
        "mean": sum(data) / len(data),
        "min": min(data),
        "max": max(data),
    }

# Pipeline
clean_and_analyze = pipe(
    remove_nulls,
    convert_to_float,
    remove_outliers,
    calculate_stats,
)

raw_data = ["10", None, "25.5", "abc", "99", "-50", None, "1500", "42"]
result = clean_and_analyze(raw_data)
print(result)
# {'count': 3, 'mean': 45.5, 'min': 10.0, 'max': 99.0}

Partial Application: functools.partial

functools.partial, bir fonksiyonun bazı argümanlarını önceden sabitler ve yeni bir fonksiyon oluşturur.

from functools import partial

# Temel örnek
def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))  # 25
print(cube(3))    # 27

Pratik Kullanım

from functools import partial

# Log fonksiyonu
def log(level, message, timestamp=None):
    from datetime import datetime
    ts = timestamp or datetime.now().strftime("%H:%M:%S")
    print(f"[{ts}] {level}: {message}")

# Seviye sabitlenmiş logger'lar
debug = partial(log, "DEBUG")
info = partial(log, "INFO")
error = partial(log, "ERROR")

debug("Değişken değeri: 42")   # [14:30:22] DEBUG: Değişken değeri: 42
info("Sunucu başlatıldı")      # [14:30:22] INFO: Sunucu başlatıldı
error("Bağlantı hatası!")      # [14:30:22] ERROR: Bağlantı hatası!

partial ve map Birlikte

from functools import partial

def multiply(x, y):
    return x * y

double = partial(multiply, 2)
triple = partial(multiply, 3)

numbers = [1, 2, 3, 4, 5]
print(list(map(double, numbers)))  # [2, 4, 6, 8, 10]
print(list(map(triple, numbers)))  # [3, 6, 9, 12, 15]

Operator Modülü

operator modülü, Python operatörlerinin fonksiyon versiyonlarını sağlar. Lambda yerine kullanılabilir.

import operator

# Aritmetik operatörler
print(operator.add(3, 5))     # 8 (3 + 5)
print(operator.mul(4, 6))     # 24 (4 * 6)
print(operator.sub(10, 3))    # 7 (10 - 3)

# Lambda yerine operator
from functools import reduce

numbers = [1, 2, 3, 4, 5]
product = reduce(operator.mul, numbers)
print(product)  # 120

# Karşılaştırma
print(operator.lt(3, 5))   # True (3 < 5)
print(operator.eq(3, 3))   # True (3 == 3)

itemgetter ve attrgetter

from operator import itemgetter, attrgetter

# itemgetter: dict/tuple'dan eleman alma
students = [
    {"name": "Ali", "grade": 85},
    {"name": "Veli", "grade": 92},
    {"name": "Ayşe", "grade": 78},
]

# Lambda yerine itemgetter
sorted_by_grade = sorted(students, key=itemgetter("grade"))
print([s["name"] for s in sorted_by_grade])
# ['Ayşe', 'Ali', 'Veli']

# Birden fazla key
sorted_multi = sorted(students, key=itemgetter("grade", "name"))

# attrgetter: nesneden attribute alma
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

students = [Student("Ali", 85), Student("Veli", 92), Student("Ayşe", 78)]
sorted_students = sorted(students, key=attrgetter("grade"))
print([s.name for s in sorted_students])
# ['Ayşe', 'Ali', 'Veli']

methodcaller

from operator import methodcaller

# String metodlarını fonksiyon olarak kullan
upper = methodcaller("upper")
strip = methodcaller("strip")
split_comma = methodcaller("split", ",")

words = ["  hello  ", "  world  ", "  python  "]
cleaned = list(map(strip, words))
print(cleaned)  # ['hello', 'world', 'python']

uppered = list(map(upper, cleaned))
print(uppered)  # ['HELLO', 'WORLD', 'PYTHON']

FP vs OOP: Ne Zaman Hangisi?

ÖzellikFPOOP
OdakDönüşümlerNesneler
StateYok (immutable)Var (mutable)
Kod stiliFonksiyon zinciriMetod çağrıları
Test kolaylığıÇok kolayOrta
Uygun alanVeri dönüşümü, pipelineKarmaşık durum yönetimi
Python desteğiİyiÇok iyi

Ne Zaman FP?

  • Veri dönüştürme pipeline'ları

  • Matematiksel hesaplamalar

  • Event processing

  • Basit, stateless operasyonlar

Ne Zaman OOP?

  • GUI uygulamaları

  • Oyun geliştirme (karakter, envanter, durum)

  • Karmaşık iş kuralları

  • Framework/kütüphane tasarımı

Python'da En İyisi: İkisinin Karışımı

# OOP: Veri yapısı
class Order:
    def __init__(self, items, discount=0):
        self.items = items
        self.discount = discount

# FP: Dönüşüm fonksiyonları
def calculate_total(order):
    subtotal = sum(item["price"] * item["qty"] for item in order.items)
    return subtotal * (1 - order.discount)

def apply_tax(total, tax_rate=0.18):
    return total * (1 + tax_rate)

def format_price(amount):
    return f"₺{amount:,.2f}"

# Pipeline
order = Order(
    items=[
        {"name": "Laptop", "price": 15000, "qty": 1},
        {"name": "Mouse", "price": 250, "qty": 2},
    ],
    discount=0.10
)

result = pipe(
    calculate_total,
    partial(apply_tax, tax_rate=0.18),
    format_price,
)(order)

print(result)  # ₺16,461.00

⚠️ Dikkat: Python'da "saf FP" yazmaya çalışma. Python fonksiyonel bir dil değil, multi-paradigma bir dil. En iyi yaklaşım: OOP ile yapıyı, FP ile dönüşümleri tanımlamak.


İleri FP Teknikleri

Currying: Tek Argümanlı Fonksiyon Zinciri

Currying, çok argümanlı bir fonksiyonu tek argümanlı fonksiyonlar zincirine dönüştürmek:

def curry(func):
    """Fonksiyonu curry'le — her argüman ayrı ayrı verilebilir."""
    import inspect
    params = inspect.signature(func).parameters
    arity = len(params)
    
    def curried(*args):
        if len(args) >= arity:
            return func(*args)
        return lambda *more: curried(*args, *more)
    
    return curried

@curry
def add(a, b, c):
    return a + b + c

# Farklı çağrı şekilleri
print(add(1, 2, 3))     # 6
print(add(1)(2)(3))      # 6 — curried!
print(add(1, 2)(3))      # 6 — partial curried
add_5 = add(5)           # İlk argümanı sabitle
print(add_5(10, 20))     # 35

Memoize Decorator (FP Tarzı)

def memoize(func):
    """Fonksiyon sonuçlarını cache'leyen pure decorator."""
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    wrapper.cache = cache
    wrapper.__name__ = func.__name__
    return wrapper

@memoize
def expensive_calculation(n):
    """Pahalı hesaplama simülasyonu."""
    print(f"  Hesaplanıyor: {n}")
    return sum(i ** 2 for i in range(n))

print(expensive_calculation(1000))  # Hesaplanıyor: 1000 → sonuç
print(expensive_calculation(1000))  # Cache'ten — hesaplama yok!
print(expensive_calculation(2000))  # Hesaplanıyor: 2000 → sonuç
print(f"Cache boyutu: {len(expensive_calculation.cache)}")  # 2

Fonksiyonel Veri İşleme Pipeline

from functools import reduce, partial
from operator import itemgetter

# Gerçek dünya: E-ticaret sipariş analizi
orders = [
    {"customer": "Ali", "product": "Laptop", "amount": 15000, "city": "İstanbul"},
    {"customer": "Veli", "product": "Mouse", "amount": 250, "city": "Ankara"},
    {"customer": "Ali", "product": "Klavye", "amount": 500, "city": "İstanbul"},
    {"customer": "Ayşe", "product": "Monitor", "amount": 5000, "city": "İzmir"},
    {"customer": "Veli", "product": "Kulaklık", "amount": 800, "city": "Ankara"},
    {"customer": "Ali", "product": "SSD", "amount": 1200, "city": "İstanbul"},
]

# FP tarzı analiz fonksiyonları
def filter_by(key, value, data):
    """Belirli alan-değer çiftine göre filtrele."""
    return [item for item in data if item[key] == value]

def sum_field(field, data):
    """Belirli alanın toplamını hesapla."""
    return sum(item[field] for item in data)

def group_by(key, data):
    """Belirli alana göre grupla."""
    groups = {}
    for item in data:
        k = item[key]
        groups.setdefault(k, []).append(item)
    return groups

# Pipeline ile analiz
istanbul_filter = partial(filter_by, "city", "İstanbul")
amount_sum = partial(sum_field, "amount")

# İstanbul'daki toplam satış
istanbul_total = amount_sum(istanbul_filter(orders))
print(f"İstanbul toplam: ₺{istanbul_total:,}")  # ₺16,700

# Müşteri bazlı grupla ve her birinin toplamını hesapla
customer_groups = group_by("customer", orders)
for customer, customer_orders in customer_groups.items():
    total = amount_sum(customer_orders)
    print(f"  {customer}: ₺{total:,} ({len(customer_orders)} sipariş)")

Either Pattern (Hata Yönetimi FP Tarzı)

class Result:
    """FP tarzı hata yönetimi: Success veya Failure."""
    
    def __init__(self, value=None, error=None):
        self._value = value
        self._error = error
        self._is_success = error is None
    
    @staticmethod
    def success(value):
        return Result(value=value)
    
    @staticmethod
    def failure(error):
        return Result(error=error)
    
    def map(self, func):
        """Başarılıysa fonksiyonu uygula, değilse hatayı koru."""
        if self._is_success:
            try:
                return Result.success(func(self._value))
            except Exception as e:
                return Result.failure(str(e))
        return self
    
    def get_or_default(self, default):
        return self._value if self._is_success else default
    
    def __repr__(self):
        if self._is_success:
            return f"Success({self._value})"
        return f"Failure({self._error})"

# Kullanım: Exception fırlatmak yerine Result döndür
def parse_int(s):
    try:
        return Result.success(int(s))
    except ValueError:
        return Result.failure(f"'{s}' sayıya çevrilemez")

# Zincirleme dönüşüm
result = (
    parse_int("42")
    .map(lambda x: x * 2)
    .map(lambda x: x + 10)
)
print(result)  # Success(94)

result = (
    parse_int("abc")
    .map(lambda x: x * 2)
    .map(lambda x: x + 10)
)
print(result)  # Failure('abc' sayıya çevrilemez)
print(result.get_or_default(0))  # 0

Yaygın Hatalar

1. Lambda Karmaşıklığı

# ❌ Okunamaz lambda
transform = lambda x: (lambda y: y ** 2)(x + 1) if x > 0 else 0

# ✅ Normal fonksiyon yaz
def transform(x):
    if x > 0:
        return (x + 1) ** 2
    return 0

2. reduce Gereksiz Kullanımı

from functools import reduce

# ❌ reduce ile toplama
total = reduce(lambda a, b: a + b, numbers)

# ✅ sum() kullan
total = sum(numbers)

# ❌ reduce ile string birleştirme
text = reduce(lambda a, b: a + " " + b, words)

# ✅ join() kullan
text = " ".join(words)

3. Mutasyonu Gizlemek

# ❌ "Fonksiyonel" görünüp mutasyon yapan kod
def process(items):
    items.sort()        # Orijinal listeyi değiştiriyor!
    return items[:5]

# ✅ Gerçekten fonksiyonel
def process(items):
    return sorted(items)[:5]  # Yeni liste döner

Özet

  • Fonksiyonel programlama, state yerine dönüşümlere, side effect yerine pure function'lara odaklanır. Matematiğin programlamaya yansımasıdır.

  • Pure function'lar aynı input için hep aynı output verir ve yan etkisi yoktur. Test, debug ve paralellik açısından büyük avantaj sağlar.

  • First-class functions sayesinde fonksiyonları değişkene atayabilir, parametre geçirebilir ve fonksiyondan döndürebilirsin. Higher-order functions bu özelliği kullanır.

  • map/filter/reduce üçlüsü FP'nin temel araçlarıdır. Python'da list comprehension genellikle daha okunaklı alternatiftir.

  • `functools.partial` ile argümanları önceden sabitleyebilir, `operator` modülü ile lambda yerine fonksiyon referansları kullanabilirsin.

  • Python'da en iyi yaklaşım FP + OOP karışımı: veri yapılarını OOP ile, dönüşümleri FP ile tanımla.