1. Giriş
Nəzəriyyədən praktikaya keçərkən məntiqli sual yaranır: “Niyə mənə, .NET və C# inkişaf etdiricisinə, bu funksional proqramlaşdırma üsulları lazımdır?”
Doğrudur, C# F# və ya Haskell kimi tam funksional dil deyil. Amma 3.0 versiyasından başlayaraq və C# 14-ə qədər o, FP alətləri ilə zənginləşib ki, bu da kodun keyfiyyətini və ifadəliliyini ciddi yaxşılaşdıra bilər.
Onlar xüsusən bu hallarda yaxşı işləyir:
- Kolleksiyalarla iş — LINQ, Map/Reduce, filter, aggregation, sort və məlumatlarla bağlı müxtəlif “sehrbazlıq”.
- Təmiz funksiyalar — state və side-effect-lər azaldığından bug-lar daha az olur, debugging asanlaşır.
- Higher-order funksiyalar — universal, reuse-olunan komponentlər, onlarla işləmək rahatdır.
- Immutability çoxthreading-də — paralel və async ssenarilərdə təhlükəsiz kodun əsas şərtlərindən biridir.
- Funksiyaların kompozisiyası — kompleks biznes loqikasını qısa, oxunaqlı və test etmək üçün əlverişli edir.
Cədvəl: C#-da OOP yanaşması və FP yanaşmasının müqayisəsi
| Tapşırıq | İmperativ (OOP/ənənəvi) | Funksional (FP) |
|---|---|---|
| Listin filter-lənməsi | |
|
| Listin transformasiyası | |
|
| Mehir kriteriyasına görə axtarış | |
|
| Aggregasiya | |
|
| Keşləmə | |
|
2. LINQ: C#-da ən funksional şey
Əgər sizə elə gəlibsə ki “funksional proqramlaşdırma” listlər, filter-lər və belə .Where, .Select, .Aggregate haqqındadır — təbrik edirəm: elədir! LINQ C#-dakı FP-nin quintessence-dir.
Gəlin xatırlayaq ki, LINQ necə işləyir
LINQ kolleksiyalarla function-parameter-lər alan zəncirlər vasitəsilə işləyir (məsələn, lambdalar). Məsələn:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Yalnız cüt ədədləri götürüb onları ikiqat edirik
var result = numbers
.Where(x => x % 2 == 0)
.Select(x => x * 2);
foreach (var number in result)
Console.WriteLine(number);
Burada nə baş verir?
- .Where — higher-order funksiya: funksiya (məsələn, x => x % 2 == 0) qəbul edir və başqa kolleksiya qaytarır.
- .Select — yenə funksiya (x => x * 2) qəbul edir.
- Biz ilkin kolleksiyanı dəyişmirik, yeni nəticə alırıq.
Bu stil oxumaq və genişləndirmək üçün rahatdır (üzərinə .OrderBy, .Take, .Distinct və s. əlavə etmək olar).
Dikkat! Lambda ifadələri — “on-the-fly” delegate yaratmağın rahat yoludur. LINQ C#-da FP dəstəyisiz mümkün olmazdı.
Sxem: Kolleksiyanın funksional işlənməsi
Kolleksiya --> Where(x => bool) --> Select(x => y) --> Yeni nəticə
3. Funksiyaların kompozisiyası və data pipeline
FP tez-tez kompozisiyadan istifadə edir: kompleks əməliyyat kiçik funksiyaların zənciri kimi qurulur, hər biri öz işini görür.
Nümunə: string emalı zənciri
State-i dəyişən (OOP) stil:
string s = " hello world ";
s = s.Trim();
s = s.ToUpper();
s = s + "!";
Console.WriteLine(s); // HELLO WORLD!
Daha “funksional” — funksiyalar konveyrı kimi:
Func<string, string> trim = x => x.Trim();
Func<string, string> upper = x => x.ToUpper();
Func<string, string> addBang = x => x + "!";
// Funksiyaların kompozisiyası — onları ardıcıl tətbiq edirik
Func<string, string> pipeline = x => addBang(upper(trim(x)));
Console.WriteLine(pipeline(" hello world ")); // HELLO WORLD!
Sadə bir composition combinator:
Func<T, R> Compose<T, U, R>(Func<T, U> f, Func<U, R> g) =>
x => g(f(x));
// İndi Compose vasitəsilə pipeline:
var pipeline2 = Compose(trim, upper);
pipeline2 = Compose(pipeline2, addBang);
Console.WriteLine(pipeline2(" hello again ")); // HELLO AGAIN!
4. Immutability ilə iş: bug-lardan qorunma
Immutability — FP-nin əsas tikinti bloku. Biz data strukturunu dəyişmirik, yeni birini qaytarırıq. Bu, multi-threaded tətbiqlərdə xüsusilə vacibdir.
Nümunə: “səhv” (mutable)
List<int> numbers = new List<int> { 1, 2, 3 };
numbers[0] = 42;
Nümunə: “düzgün” (funksional)
var numbers = new List<int> { 1, 2, 3 };
var newNumbers = numbers.Select((x, i) => i == 0 ? 42 : x).ToList();
Müasir C#-da ImmutableList<T> və System.Collections.Immutable namespace-dən digər tiplər var:
using System.Collections.Immutable;
var immutableNumbers = ImmutableList.Create(1, 2, 3);
var changed = immutableNumbers.SetItem(0, 42); // Yeni siyahı qaytarır!
5. Higher-order funksiyalar real həyatda
Higher-order funksiyalar — şərt operatorlarının çoxluğundan qaçmaq üçün universal komponentlər yazmağın yolu.
Nümunə: istifadəçilər üçün universal filter
class User
{
public string Name { get; set; }
public int Age { get; set; }
}
var users = new List<User>
{
new User { Name = "Vasya", Age = 26 },
new User { Name = "Katya", Age = 17 },
new User { Name = "Lyosha", Age = 35 }
};
List<User> FilterUsers(List<User> source, Predicate<User> predicate)
{
return source.Where(u => predicate(u)).ToList();
}
// İstifadə:
var adults = FilterUsers(users, u => u.Age >= 18);
var longNames = FilterUsers(users, u => u.Name.Length > 3);
6. Pattern matching və switch ifadələri
Müasir C# pattern matching-dən fəal istifadə edir: switch-ifadələri tez-tez uzun if zəncirlərini əvəz edir.
object value = 123;
string description = value switch
{
int i when i > 100 => "Böyük ədəd",
string s when s.Length > 3 => "Sətir uzun",
null => "Boş dəyər",
_ => "Naməlum"
};
Console.WriteLine(description); // Böyük ədəd
7. Memoization: funksiyaların nəticələrinin keşi
Memoization — eyni arqumentlər üçün funksiyanın nəticəsini keşləməkdir. C#-da bunu özümüz rahatlıqla həyata keçirə bilərik.
Func<int, int> SlowFib = null; // Fibonacci üçün rekursiv funksiya
var cache = new Dictionary<int, int>();
SlowFib = n =>
{
if (cache.ContainsKey(n))
return cache[n];
if (n <= 1)
cache[n] = n;
else
cache[n] = SlowFib(n - 1) + SlowFib(n - 2);
return cache[n];
};
Console.WriteLine(SlowFib(40)); // Çok sürətli!
8. Currying və partial application
Partial application — funksiyanın bəzi arqumentlərini bağlamaqdır. C#-da bunu lambda-larla rahat etmək olar.
Func<int, int, int> add = (a, b) => a + b;
// Birinci arqumenti bağladıq
Func<int, int> add10 = b => add(10, b);
Console.WriteLine(add10(5)); // 15
Console.WriteLine(add10(100)); // 110
9. Deklarativ stil funksiyalarla
İmperativ:
var result = new List<int>();
foreach (var n in numbers)
{
if (n > 0)
result.Add(n * n);
}
Deklarativ:
var result = numbers
.Where(n => n > 0)
.Select(n => n * n)
.ToList();
10. Praktik tapşırıq
“Tələbələr üçün task manager”də task filter modulunu realizə edək.
Model:
class StudentTask
{
public string Title { get; set; }
public bool IsCompleted { get; set; }
public int Priority { get; set; }
}
Başlanğıc data:
var tasks = new List<StudentTask>
{
new StudentTask { Title = "Sdelat domashku", IsCompleted = false, Priority = 2 },
new StudentTask { Title = "Popit kofe", IsCompleted = true, Priority = 3 },
new StudentTask { Title = "Posmotret lektciyu", IsCompleted = false, Priority = 1 }
};
Universal filter:
List<StudentTask> FilterTasks(
List<StudentTask> all,
Predicate<StudentTask> predicate)
{
return all.Where(t => predicate(t)).ToList();
}
// Tamamlanmamış və Priority > 1 olan tapşırıqları axtarırıq
var importantTasks = FilterTasks(tasks, t => !t.IsCompleted && t.Priority > 1);
// Nəticəni çap edirik
foreach (var task in importantTasks)
Console.WriteLine(task.Title);
Predicate combinator-lar:
Predicate<StudentTask> IsActive = t => !t.IsCompleted;
Predicate<StudentTask> IsHighPriority = t => t.Priority > 1;
// Bir neçə kriteriyanı birləşdirmək, variant 1
var specialTasks = FilterTasks(tasks, t => IsActive(t) && IsHighPriority(t));
// Variant 2: iki predicate-in combinator funksiyası
Predicate<StudentTask> And(Predicate<StudentTask> a, Predicate<StudentTask> b) => t => a(t) && b(t);
var specialTasks2 = FilterTasks(tasks, And(IsActive, IsHighPriority));
11. FP yanaşmasını C#-da tətbiq edərkən xüsusiyyətlər və tipik səhvlər
Birincisi, unutmayın ki, C# strongly-typed dildir. Bəzən, xüsusilə funksiyalar delegate və ya kompleks lambda qaytaranda, tipləri açıq göstərmək lazımdır. Əks halda kompilyasiya zamanı type inference problemi ola bilər.
İkincisi, side-effect olan funksiyaları yerlərdə istifadə etməyin ki, orada təmiz funksiyalar gözlənilir. Xarici dəyişənlərin dəyişdirilməsi predictability-ni pozur. Funksiyalarınızı mümkün qədər öz sahəsindən kənarda state-i dəyişməyəcək şəkildə yazın.
Üçüncüsü, closure capture ilə ehtiyatlı olun, xüsusilə async və multi-threaded kodda. Lambda-ya verilən və sonradan dəyişən bir dəyişən (məsələn, LINQ içində) gözlənilməz bug-lara gətirə bilər.
Nəhayət, FP stilinin artıq istifadəsi komandada olmayanlar üçün kodun dəstəyini çətinləşdirə bilər. FP-dən yalnız həqiqətən asanlaşdırdığı yerlərdə istifadə edin, “gözəllik” üçün deyil.
GO TO FULL VERSION