เมื่อไม่นานมานี้ฉันเริ่มใช้ Scala แทน Java ส่วนหนึ่งของกระบวนการ "แปลง" ระหว่างภาษาสำหรับฉันได้เรียนรู้การใช้งานEither
อัตโนมัติแทน (ตรวจสอบ) Exception
s ฉันได้รับการเข้ารหัสด้วยวิธีนี้มาระยะหนึ่งแล้ว แต่เมื่อไม่นานมานี้ฉันเริ่มสงสัยว่าเป็นวิธีที่ดีกว่าหรือไม่
ข้อEither
ได้เปรียบที่สำคัญอย่างหนึ่งที่มากกว่าException
คือประสิทธิภาพที่ดีกว่า Exception
ความต้องการที่จะสร้างสแต็คร่องรอยและจะถูกโยน เท่าที่ฉันเข้าใจการขว้างปาException
ไม่ใช่ส่วนที่เรียกร้อง แต่การสร้างสแต็กติดตามคือ
แต่แล้วหนึ่งสามารถสร้าง / สืบทอดException
s พร้อมด้วยscala.util.control.NoStackTrace
และมากยิ่งขึ้นดังนั้นผมเห็นความอุดมสมบูรณ์ของกรณีที่ด้านซ้ายของEither
ในความเป็นจริงException
(ละทิ้งการเพิ่มประสิทธิภาพการทำงาน)
ข้อดีอีกข้อหนึ่งEither
ก็คือคอมไพเลอร์ - ความปลอดภัย คอมไพเลอร์ Scala จะไม่บ่นเกี่ยวกับException
s ที่ไม่ได้จัดการ(ต่างจากคอมไพเลอร์ของ Java) แต่ถ้าฉันไม่เข้าใจผิดการตัดสินใจครั้งนี้มีเหตุผลด้วยเหตุผลเดียวกับที่กำลังคุยกันในหัวข้อนี้ดังนั้น ...
ในแง่ของไวยากรณ์ฉันรู้สึกว่าException
สไตล์นั้นชัดเจนกว่า ตรวจสอบบล็อคโค้ดต่อไปนี้ (ทั้งการใช้ฟังก์ชันเดียวกัน):
Either
รูปแบบ:
def compute(): Either[String, Int] = {
val aEither: Either[String, String] = if (someCondition) Right("good") else Left("bad")
val bEithers: Iterable[Either[String, Int]] = someSeq.map {
item => if (someCondition(item)) Right(item.toInt) else Left("bad")
}
for {
a <- aEither.right
bs <- reduce(bEithers).right
ignore <- validate(bs).right
} yield compute(a, bs)
}
def reduce[A,B](eithers: Iterable[Either[A,B]]): Either[A, Iterable[B]] = ??? // utility code
def validate(bs: Iterable[Int]): Either[String, Unit] = if (bs.sum > 22) Left("bad") else Right()
def compute(a: String, bs: Iterable[Int]): Int = ???
Exception
รูปแบบ:
@throws(classOf[ComputationException])
def compute(): Int = {
val a = if (someCondition) "good" else throw new ComputationException("bad")
val bs = someSeq.map {
item => if (someCondition(item)) item.toInt else throw new ComputationException("bad")
}
if (bs.sum > 22) throw new ComputationException("bad")
compute(a, bs)
}
def compute(a: String, bs: Iterable[Int]): Int = ???
หลังดูดีกว่าสำหรับฉันมากและโค้ดที่จัดการกับความล้มเหลว (ทั้งการจับคู่รูปแบบEither
หรือtry-catch
) นั้นค่อนข้างชัดเจนทั้งสองกรณี
ดังนั้นคำถามของฉันคือ - ทำไมใช้Either
เกิน (ตรวจสอบ) Exception
?
ปรับปรุง
หลังจากอ่านคำตอบแล้วฉันก็ตระหนักว่าฉันอาจไม่ได้นำเสนอแก่นแท้ของภาวะที่กลืนไม่เข้าคายไม่ออกของฉัน ความกังวลของฉันไม่ได้อยู่กับการขาดtry-catch
; หนึ่งสามารถ "จับ" ซึ่งเป็นException
ที่มีTry
หรือใช้เพื่อตัดข้อยกเว้นด้วยcatch
Left
ปัญหาหลักของฉันกับEither
/ Try
มาเมื่อฉันเขียนรหัสที่อาจล้มเหลวในหลายจุดตลอดทาง; ในสถานการณ์เหล่านี้เมื่อพบความล้มเหลวฉันต้องเผยแพร่ความล้มเหลวนั้นตลอดทั้งรหัสของฉันดังนั้นจึงทำให้โค้ดมีความยุ่งยากมากขึ้น (ดังแสดงในตัวอย่างที่กล่าวมา)
จริงๆแล้วมีวิธีทำลายรหัสโดยไม่ใช้Exception
s อีกอันreturn
(อันที่จริงแล้วเป็นอีก "taboo" ใน Scala) รหัสจะยังคงชัดเจนกว่าEither
วิธีการและในขณะที่เป็นบิตสะอาดน้อยกว่าException
รูปแบบจะมีความกลัวของการไม่จับไม่มีException
s
def compute(): Either[String, Int] = {
val a = if (someCondition) "good" else return Left("bad")
val bs: Iterable[Int] = someSeq.map {
item => if (someCondition(item)) item.toInt else return Left("bad")
}
if (bs.sum > 22) return Left("bad")
val c = computeC(bs).rightOrReturn(return _)
Right(computeAll(a, bs, c))
}
def computeC(bs: Iterable[Int]): Either[String, Int] = ???
def computeAll(a: String, bs: Iterable[Int], c: Int): Int = ???
implicit class ConvertEither[L, R](either: Either[L, R]) {
def rightOrReturn(f: (Left[L, R]) => R): R = either match {
case Right(r) => r
case Left(l) => f(Left(l))
}
}
โดยพื้นฐานแล้วการreturn Left
แทนที่throw new Exception
และวิธีการโดยปริยายบนrightOrReturn
, เป็นส่วนเสริมสำหรับการเผยแพร่ข้อยกเว้นอัตโนมัติขึ้นสแต็ก
Try
ดูเหมือนว่าส่วนใหญ่กล่าวถึงในบทความ ส่วนเกี่ยวกับEither
vs Exception
เพียงระบุว่าEither
s ควรใช้เมื่อกรณีอื่น ๆ ของวิธีการคือ "ไม่พิเศษ" ก่อนอื่นนี่เป็นคำนิยามที่ชัดเจนมาก ๆ ประการที่สองมันคุ้มค่ากับการลงโทษไวยากรณ์หรือไม่ ฉันหมายความว่าฉันไม่รังเกียจที่จะใช้Either
s ถ้ามันไม่ได้อยู่เหนือไวยากรณ์ที่พวกเขามีอยู่
Either
ดูเหมือนว่า monad สำหรับฉัน ใช้มันเมื่อคุณต้องการผลประโยชน์การจัดองค์ประกอบการทำงานที่ monads จัดหาให้ หรืออาจจะไม่ได้
Either
ด้วยตัวเองไม่ใช่ monad การฉายภาพทั้งด้านซ้ายหรือด้านขวาเป็นภาพเดียว แต่Either
โดยตัวมันเองไม่ได้ คุณสามารถทำให้มันเป็น monad ได้โดย "biasing" ไปทางซ้ายหรือทางขวา Either
แต่แล้วคุณบอกความหมายบางอย่างในทั้งสองด้านของ สกาลาEither
เดิมทีไม่ฝักใฝ่ฝ่ายใด แต่ค่อนข้างเอนเอียงเมื่อเร็ว ๆ นี้ดังนั้นในปัจจุบันความจริงแล้วมันคือ monad แต่ "monadness" ไม่ใช่สมบัติโดยธรรมชาติของEither
มัน แต่เป็นผลมาจากการลำเอียง