Either هو نوع وظيفي يمثل قيمة من أحد احتمالين: Left للخطأ و Right للنجاح. على عكس الاستثناءات، يجعل Either معالجة الأخطاء صريحة على مستوى الأنواع ولا يتطلب كتل try-catch. وفقاً Arrow، 2026، يُستخدم Either على نطاق واسع في مشاريع Kotlin لتكوين العمليات التي قد تفشل، دون آثار جانبية.
النقاط الرئيسية
Either هو نوع بيانات جبري، مستعار من البرمجة الوظيفية، يمثل واحداً بالضبط من نوعين محتملين. في سياق معالجة الأخطاء، الاتفاق هو: النوع Left (Left) يحتوي على وصف المشكلة، بينما النوع Right (Right) يحتوي على نتيجة ناجحة.
يعود مفهوم Either إلى لغة Haskell وفئة الأنواع Either a b، حيث a هو نوع القيمة اليسرى و b نوع القيمة اليمنى. في تطوير التطبيقات المحمولة، انتشر Either بفضل مكتبة Arrow للغة Kotlin والمناهج الوظيفية في Swift.
الميزة الرئيسية لـ Either على الاستثناءات هي غياب مسارات التنفيذ المخفية. الدالة التي تُرجع Either تُعلن صراحةً في توقيعها أنها قد تفشل. يتحقق المترجم من معالجة كلا الاحتمالين.
على عكس رمي الاستثناءات، يحافظ Either على شفافية تدفق البيانات. استدعاء دالة تُرجع Either لا يتطلب try-catch من جهة المستدعي — يكفي pattern-matching أو fold. هذا مهم بشكل خاص في البنى ذات النهج التفاعلي، حيث يُرجع كل مصدر بيانات Either وتُبنى سلاسل التحويل عبر map و flatMap.
يتكون Either من نوعين فرعيين: Left و Right. يمكن أن تكون نسخة Either واحداً منهما فقط في أي لحظة. التوقيع النموذجي في Kotlin هو Either<E, A>، حيث E هو نوع الخطأ و A هو نوع القيمة الناجحة.
Left يمثل حالة الفشل. على عكس الاستثناءات، لا يقطع Left تدفق التنفيذ — بل يُرجع ببساطة قيمة تحتاج إلى المعالجة. يمكن أن يكون نوع الخطأ أي شيء: String، Int، فئة مختومة مخصصة أو نموذج مجال.
Right يحتوي على النتيجة الصحيحة للعملية. الاسم يعكس الصحة — بالاتفاق، Right يعني النجاح. تُطبق تحويلات map و flatMap على Right، مما يسمح ببناء سلاسل حسابية دون التحقق من كل خطوة.
flatMap هي الآلية الرئيسية لتركيب Either. إذا كانت القيمة الحالية هي Right، يُطبق flatMap الدالة المُمرَرة ويُعيد Either جديداً. إذا كانت Left، يتجاوز flatMap التحويل ويُمرر الخطأ. هذا السلوك يُسمى تقييم الدارة القصيرة.
بالإضافة إلى flatMap، يدعم Either mapLeft لتحويل الخطأ، fold لمعالجة كلا الاحتمالين في مكان واحد، و getOrElse لاستخراج قيمة مع قيمة افتراضية. تغطي هذه الدوال جميع السيناريوهات: من الاستخراج البسيط إلى التركيب المعقد مع الاستدعاءات غير المتزامنة في coroutines في Kotlin أو Combine في Swift.
fun parseInt(input: String): Either<String, Int> =
input.toIntOrNull()?.let { Right(it) }
?: Left("تعذّر التحويل: $input")
fun divide(a: Int, b: Int): Either<String, Int> =
if (b == 0) Left("القسمة على صفر")
else Right(a / b)
val result = parseInt("10")
.flatMap { divide(it, 2) }
في نظام Kotlin البيئي، تم تنفيذ Either في مكتبة Arrow. تقدم المكتبة القياسية لـ Kotlin Result، لكن Either يوفر مرونة أكبر: نوع خطأ مخصص، تركيب عبر flatMap ودعم للأنماط الوظيفية.
Arrow هي مكتبة وظيفية للغة Kotlin تضيف Either و Option و Validated وأنواعاً أخرى. Arrow.Either هي فئة مختومة بنوعين فرعيين: ArrowCore.Left و ArrowCore.Right. توفر المكتبة أيضاً امتدادات ملائمة: .getOrElse()، .fold()، .mapLeft().
لننظر إلى سيناريو واقعي — طلب شبكة في تطبيق Android مع أخطاء محتملة: انقطاع الشبكة، خطأ خادم، استجابة غير صحيحة. يتيح Either دمج جميع الاحتمالات في نوع إرجاع واحد.
sealed class NetworkError {
data class NoConnection(val message: String): NetworkError()
data class ServerError(val code: Int): NetworkError()
data class ParseError(val detail: String): NetworkError()
}
suspend fun fetchUser(id: String): Either<NetworkError, User> {
return try {
val response = api.getUser(id)
if (response.isSuccessful) {
Right(response.body()!!)
} else {
Left(NetworkError.ServerError(response.code()))
}
} catch (e: IOException) {
Left(NetworkError.NoConnection(e.message ?: ""))
}
}
في Swift، ابتداءً من الإصدار 5.0، يتوفر النوع المدمج Result المشابه conceptually لـ Either لكن مع قيود: يجب أن يتوافق الخطأ مع بروتوكول Error، وقيمة النجاح يجب أن تكون من نوع واحد. يُنفذ Either في Swift عبر enum مع معاملين عامين.
Result<Success, Failure> هو النوع المدمج في Swift، حيث Failure: Error. لا يفرض Either قيوداً على نوع الخطأ، مما يسمح بتخزين String أو هياكل مخصصة أو حتى أنواع خطأ متعددة عبر enums متداخلة.
enum Either<E, A> {
case left(E)
case right(A)
func map<B>(_ transform: (A) -> B) -> Either<E, B> {
switch self {
case .left(let e): return .left(e)
case .right(let a): return .right(transform(a))
}
}
}
let result: Either<String, Int> = .right(42)
let mapped = result.map { $0 * 2 }
Either مثالي للسيناريوهات التي تتطلب معالجة أخطاء صريحة وآمنة النوع دون استثناءات. لننظر إلى حالات الاستخدام الرئيسية في تطوير التطبيقات المحمولة بلغتي Kotlin و Swift.
لا تستخدم Either للعمليات البسيطة دون آثار جانبية — قيمة الإرجاع العادية أكثر موثوقية وأسهل للقراءة. Either أيضاً مبالغ فيه عندما يكون الفشل حالة استثنائية وليس سيناريو متوقعاً.
Either فعال أيضاً عند العمل مع coroutines في Kotlin. يمكن استدعاء دالة تُرجع Either داخل coroutine مع معالجة الأخطاء عبر fold أو mapLeft دون حظر الخيط. هذا مفيد بشكل خاص في تطبيقات Android ذات بنية MVVM، حيث يُرجع كل مستودع Either ويحول ViewModel النتيجة إلى UiState.
المطورون الجدد على Either غالباً ما يرتكبون أخطاء مماثلة. لنستعرض الأكثر شيوعاً وكيفية تجنبها.
الأسئلة الشائعة
Optional يمثل وجود أو غياب قيمة (Some/None)، لكنه لا يخبر عن سبب الغياب. يوفر Either نوعين محددين — أيسر للخطأ وأيمن للنجاح — مما يسمح بنقل سياق الفشل.
في Java لا يوجد Either مدمج، لكن المكتبات Vavr و functionaljava توفر التطبيق. في تطوير Android، Either من Vavr بديل شائع للأسلوب الوظيفي مع lambdas.
Sealed class في Kotlin أكثر ملاءمة عندما يكون هناك أكثر من احتمالين أو لها هياكل مختلفة. للنتيجة الثنائية (خطأ/نجاح)، Either أكثر compactness ويوفر أدوات دمج وظيفية جاهزة.
Either غير قابل للتغيير وآمن للخيوط افتراضياً. في Kotlin مع coroutines، يتحد Either بشكل ممتاز: يعمل flatMap داخل نطاق coroutine ومعالجة الأخطاء لا تتطلب أقفالاً.
لا. Either مناسب للعمليات ذات الأعطال المتوقعة (شبكة، تحقق، منطق أعمال). للدوال البسيطة والحسابات دون آثار جانبية، النوع العادي أسهل للقراءة ولا يضيف تعقيداً غير ضروري.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا