استخدام الخيار في كل مكان يشعر بالحرج بعض الشيء. أفعل شيئا خاطئا؟
-
21-09-2019 - |
سؤال
نتيجة للمقالات التي قرأتها عن Option
الفصل الذي يساعدك على تجنب NullPointerException ، بدأت في استخدامه في كل مكان. تخيل شيئًا كهذا:
var file:Option[File] = None
وبعد ذلك عندما أستخدمه:
val actualFile = file.getOrElse(new File("nonexisting"))
if(actualFile.getName.equals("nonexisting")) { // instead of null checking
}
else { // value of file was good
}
إن القيام بأشياء مثل هذا لا يشعر بكل هذا "صحيح" بالنسبة لي. لاحظت ذلك أيضًا .get
أصبح مستهجن. . هل هذا النوع من الأشياء ما تفعله يا رفاق مع الخيار أيضًا ، أو هل سأذهب بطريقة خاطئة؟
المحلول
عمومًا ليست فكرة جيدة للعودة Option
ثم استخدم getOrElse
لإنتاج بعض قيمة الحارس التي تعني "لم يتم العثور عليها". وهذا ما Option
تم تصميمه من أجل: للدلالة على عدم العثور على القيمة!
Option
يظهر حقًا قوتها عند استخدامها بالاقتران مع بنيات البرمجة الوظيفية مثل map
و foreach
. هذا هو الأكثر قوة عند التعامل مع خيارات متعددة. على سبيل المثال ، افترض أنني أكتب طريقة تأخذ سلسلة وتعيدني إلى ملف ، ولكن فقط في حالة وجود الملف وهو ملف وليس دليلًا:
import java.io._;
def niceFile1(s: String): File = {
val f = new File(s);
if (f.exists && !f.isDirectory) f else null
}
def niceFile2(s: String): Option[File] = {
val f = new File(s);
if (f.exists && !f.isDirectory) Some(f) else None
}
حتى الآن ، باستخدام null
أسهل-على الأقل حتى تنسى أن هذا قد يعطيك null
وستحصل على NPE. على أي حال ، دعنا الآن نحاول استخدامه.
def niceFopen1(s: String) = {
val f = niceFile1(s);
if (f!=null) new FileInputStream(f) else null;
}
def niceFopen2(s: String) = niceFile2(s).map(f => new FileInputStream(f))
انظر ماذا حدث! في الحالة السابقة ، كان علينا إجراء اختبارات منطقية باليد وإنشاء متغيرات مؤقتة. قرف! في الحالة الثانية ، map
فعلت كل العمل القذر بالنسبة لنا: لم يتم تعيين أي منها على لا شيء ، و Some(file)
تم تعيينه ل Some(fileinputstream)
. سهل!
لكنه يتحسن بعد. ربما نريد العثور على حجم مجموعة كاملة من الملفات:
def totalSize2(ss: Seq[String]) = {
(0L /: ss.flatMap(niceFile2)){(sum,f) => sum+f.length}
}
انتظر ، ما الذي يحدث هنا-ماذا عن كل None
؟ ألا يتعين علينا الانتباه والتعامل معهم بطريقة أو بأخرى؟ حسنًا ، هذا هو المكان flatMap
يأتي في: إنه ينضم إلى جميع الإجابات في قائمة واحدة. None
هو إجابة للطول صفر ، لذلك يتجاهل ذلك. Some(f)
لديه إجابة واحدة-f
-لذلك يضعها في القائمة. ثم نستخدم طية لإضافة جميع الأطوال-الآن أن جميع العناصر في القائمة صالحة. لطيفة جدا!
نصائح أخرى
انها فكرة جيدة ل لا عزم قيمة Option
, ، لكن من أجل يتقدم المنطق لكل ما يحتوي على:
findFile.foreach(process(_))
في الأساس هذه العمليات أ File
إذا تم العثور على المرء ولم يفعل شيئًا خلاف ذلك (وهو ما يعادل توماس أولاً for
الفهم بسبب for
يجمع إلى مكالمة إلى foreach
). إنها نسخة أكثر إيجازًا من:
findFile match {
case Some(f) => process(f)
case None =>
}
ما هو أكثر من ذلك ، الشيء العظيم في ذلك هو أنه يمكنك سلسلة العمليات ، شيء مثل:
(findLiveFile orElse fileBackupFile orElse findTempFile).foreach(process(_)
في معظم الحالات ، ستستخدم مطابقة الأنماط
file match {
case Some(f) => { .. } //file is there
case _ => { .. } //file is not there
}
إذا كنت مهتمًا بالملف فقط إذا كان هناك ، فيمكنك استخدام التعبير
for(f <- file) { //file is there
}
يمكنك بعد ذلك سلسلة من التعبيرات للعمل على مستويات متعددة على الحاويات
for{
option <- List(Some(1), None, Some(2))
f <- option
} yield f
res0: List[Int] = List(1, 2)
بدلاً من ذلك ، يمكنك استخدام ISDEDING والحصول على:
if(option.isDefined) {
val x = option.get;
} else {
}
GET لم يتم إهماله في Scala 2.8.0.beta-1.
هذا بديل:
var file:Option[File] = None
// ...
file map (new File(_)) foreach { fh =>
// ...
}
ومع ذلك ، إذا كنت بحاجة إلى القيام بشيء ما إذا كان الملف موجودًا ، وشيء آخر إذا لم يحدث ذلك ، أ match
البيان أكثر ملاءمة:
var file:Option[File] = None
// ...
file map (new File(_)) match {
case Some(fh) =>
// ...
case None =>
// ...
}
هذا هو نفسه إلى حد كبير مثل if
بيان ، لكني أحب أن تتسلسل الطبيعة بشكل أفضل لأشياء مثل Option
, ، حيث أريد استخراج قيمة كذلك.
في الغالب يعيد ما يقوله الجميع ، لكنني أعتقد أن هناك 4 مواقف مختلفة على الأقل ستواجهها عندما يكون لديك var قابلة للإلغاء مثل هذا:
1. يجب ألا يكون الملف فارغًا
load(file.get())
سوف ترمي استثناء عندما تحاول استخدام الملف فعليًا. فشل بسرعة ، ياي!
2. الملف فارغ ، ولكن في هذه الحالة لدينا افتراضي معقول:
load(file.getOrElse(new File("/defaultFile")))
3. فرعان للمنطق بناءً على ما إذا كان الملف موجودًا:
file match {
case Some(f) => { .. } //file is there
case _ => { .. } //file is not there
}
4. لا يوجد إذا كان الملف فارغًا ، ويعرف أيضًا باسم فشل بصمت. لا أعتقد أن هذا هو الأفضل بالنسبة لي في الحياة الواقعية:
for (f <- file) {
//do some stuff
}
أو ، ربما أكثر وضوحا؟
if (f.isDefined) {
//do some stuff
}