CodeGym /Kurslar /C# SELF /Praktik şəkildə FP-nin C#-da tətbiqi

Praktik şəkildə FP-nin C#-da tətbiqi

C# SELF
Səviyyə, Dərs
Mövcuddur

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
foreach + if + Add
.Where(predicate)
Listin transformasiyası
foreach + hesablama + Add
.Select(lambda)
Mehir kriteriyasına görə axtarış
foreach + if/return
.FirstOrDefault(predicate)
Aggregasiya
döngü ilə counter dəyişəni
.Aggregate(seed, func)
Keşləmə
əl ilə dictionary + yoxlamalar
closure ilə funksiya

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.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Funksiya kompozisiyası ilə sətirlərin işlənməsi
Funksiya kompozisiyası ilə sətirlərin işlənməsi
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
Funksional proqramlaşdırma
Funksional proqramlaşdırmaya giriş
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION