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:
Pure functions: Aynı girdi → hep aynı çıktı. Yan etkisi yok.
Immutability: Veriyi değiştirmek yerine yeni veri oluştur.
First-class functions: Fonksiyonlar da birer değer — değişkene ata, parametre olarak geçir, fonksiyondan döndür.
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:
Aynı argümanlarla çağrıldığında her zaman aynı sonucu döner
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)) # 8Higher-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)) # 49Fonksiyon 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ırlarGerç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) # 5Zincirleme 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/filteryerine 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)) # 121Pipe: 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_worldGerç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)) # 27Pratik 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?
| Özellik | FP | OOP |
|---|---|---|
| Odak | Dönüşümler | Nesneler |
| State | Yok (immutable) | Var (mutable) |
| Kod stili | Fonksiyon zinciri | Metod çağrıları |
| Test kolaylığı | Çok kolay | Orta |
| Uygun alan | Veri dönüşümü, pipeline | Karmaşı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)) # 35Memoize 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)}") # 2Fonksiyonel 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)) # 0Yaygı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 02. 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.
AI Asistan
Sorularını yanıtlamaya hazır