البرمجة الوظيفية في Kotlin: المستوى المتقدم
Kotlin هي لغة متعددة النماذج تجمع بسلاسة بين الأساليب الكائنية والوظيفية. إذا كنت قد أتقنت بالفعل مفاهيم البرمجة الوظيفية الأساسية مثل map و filter و fold و lambdas، فقد حان الوقت للغوص في التقنيات المتقدمة. في هذه المقالة، سوف نستكشف تركيب الدوال، والكاري، والتقييم الكسول، وسنتعرف أيضًا على functors و monads باستخدام أمثلة Kotlin. يمكن تشغيل جميع أمثلة الكود في بيئة التطوير الخاصة بك.
1. تركيب الدوال والكاري
التركيب هو إنشاء دالة جديدة من خلال تطبيق دالتين أو أكثر بشكل تسلسلي. لا تحتوي Kotlin على عامل تركيب مدمج مثل Haskell، ولكن يمكن تنفيذه بسهولة عبر دوال الامتداد.
مثال على تركيب الدوال
// تعريف الدوالval double: (Int) -> Int = { it * 2 }val increment: (Int) -> Int = { it + 1 }
// التركيب: أولاً double، ثم incrementinfix fun <A, B, C> ((B) -> C).compose(f: (A) -> B): (A) -> C = { a -> this(f(a)) }
val doubleThenIncrement = increment compose doubleprintln(doubleThenIncrement(5)) // 11 (5*2 + 1)الكاري
الكاري هو تحويل دالة تأخذ عدة وسائط إلى سلسلة من الدوال، كل منها تأخذ وسيطة واحدة. في Kotlin، يتم تنفيذ ذلك باستخدام lambdas المتداخلة.
// دالة عاديةfun sum(a: Int, b: Int): Int = a + b
// النسخة المُكاريّةfun curriedSum(a: Int): (Int) -> Int = { b -> a + b }
val add5 = curriedSum(5)println(add5(3)) // 8
// دالة عامة للكاريfun <A, B, C> ((A, B) -> C).curry(): (A) -> (B) -> C = { a -> { b -> this(a, b) } }
val curriedSum2 = ::sum.curry()println(curriedSum2(10)(20)) // 30الكاري مفيد بشكل خاص عند العمل مع التطبيق الجزئي ومصانع الدوال.
2. functors و monads: النظرية في التطبيق
functor هو حاوية يمكن تعيينها باستخدام دالة. monad هو functor مع القدرة على تركيب الحاويات (flatMap). في Kotlin، مثال بارز على monad هو Result (أو Either)، وكذلك Sequence.
مثال مع Result (Either monad)
data class User(val name: String, val email: String)
fun parseUser(json: String): Result<User> = runCatching { // محاكاة التحليل if (json.isBlank()) throw IllegalArgumentException("JSON فارغ") User("أليس", "alice@example.com")}
fun sendEmail(user: User): Result<String> = runCatching { if (user.email.contains("@")) "تم إرسال البريد الإلكتروني إلى ${user.email}" else throw IllegalArgumentException("بريد إلكتروني غير صالح")}
// سلسلة Monadicval result = parseUser("{...}") .flatMap { user -> sendEmail(user) } .map { message -> "نجاح: $message" } .onFailure { error -> println("خطأ: ${error.message}") }
println(result.getOrDefault("فشل"))هنا، يسمح flatMap بتجنب التحققات المتداخلة للنجاح/الخطأ، مما يجعل الكود خطيًا وقابلاً للقراءة.
إنشاء functor خاص بك: نوع Option
sealed class Option<out T> { data class Some<T>(val value: T) : Option<T>() object None : Option<Nothing>()}
fun <T, R> Option<T>.map(f: (T) -> R): Option<R> = when (this) { is Option.Some -> Option.Some(f(value)) is Option.None -> Option.None}
fun <T, R> Option<T>.flatMap(f: (T) -> Option<R>): Option<R> = when (this) { is Option.Some -> f(value) is Option.None -> Option.None}
val maybeNumber: Option<Int> = Option.Some(42)val resultOption = maybeNumber .map { it * 2 } .flatMap { Option.Some("القيمة: $it") }
println(resultOption) // Some(value=القيمة: 84)