Belum lama ini saya mulai menggunakan Scala, bukan Java. Bagian dari proses "konversi" antara bahasa untuk saya adalah belajar menggunakan Eithers bukan (dicentang) Exceptions. Saya telah mengkode cara ini untuk sementara waktu, tetapi baru-baru ini saya mulai bertanya-tanya apakah itu benar-benar cara yang lebih baik untuk pergi.
Salah satu keunggulan utama Eithertelah berakhir Exceptionadalah kinerja yang lebih baik; sebuah Exceptionkebutuhan untuk membangun tumpukan-jejak dan sedang dilemparkan. Sejauh yang saya mengerti, melempar Exceptionbukan bagian yang menuntut, tetapi membangun stack-trace adalah.
Tapi kemudian, kita selalu bisa membuat / mewarisi Exceptiondengan scala.util.control.NoStackTrace, dan lebih dari itu, saya melihat banyak kasus di mana sisi kiri sebuah Eithersebenarnya adalah Exception(meneruskan peningkatan kinerja).
Satu keuntungan lain yang dimiliki Eitheradalah keamanan kompiler; kompiler Scala tidak akan mengeluh tentang yang tidak ditangani Exception(tidak seperti kompiler Java). Tetapi jika saya tidak salah, keputusan ini beralasan dengan alasan yang sama yang sedang dibahas dalam topik ini, jadi ...
Dalam hal sintaks, saya merasa seperti- Exceptiongaya jauh lebih jelas. Periksa blok kode berikut (keduanya mencapai fungsi yang sama):
Either gaya:
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 gaya:
@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 = ???
Yang terakhir terlihat jauh lebih bersih bagi saya, dan kode yang menangani kegagalan (baik pencocokan pola Eitheratau try-catch) cukup jelas dalam kedua kasus.
Jadi pertanyaan saya adalah - mengapa menggunakan Eitherlebih dari (dicentang) Exception?
Memperbarui
Setelah membaca jawaban, saya menyadari bahwa saya mungkin gagal menyajikan inti dari dilema saya. Kekhawatiran saya bukan pada kurangnya try-catch; seseorang dapat "menangkap" Exceptiondengan Try, atau menggunakan catchuntuk membungkus pengecualian Left.
Masalah utama saya dengan Either/ Trymuncul ketika saya menulis kode yang mungkin gagal di banyak titik di sepanjang jalan; dalam skenario ini, ketika menemui kegagalan, saya harus menyebarkan kegagalan itu di seluruh kode saya, sehingga membuat kode cara yang lebih rumit (seperti yang ditunjukkan dalam contoh-contoh tersebut).
Sebenarnya ada cara lain untuk memecahkan kode tanpa Exceptionmenggunakan return(yang sebenarnya adalah "tabu" di Scala). Kode masih lebih jelas daripada Eitherpendekatan, dan sementara menjadi sedikit kurang bersih daripada Exceptiongaya, tidak akan ada rasa takut dari yang tidak tertangkap Exception.
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))
}
}
Pada dasarnya return Leftpenggantian throw new Exception, dan metode implisit pada keduanya rightOrReturn, adalah suplemen untuk propagasi pengecualian otomatis di stack.
Try. Bagian tentang Eithervs Exceptionhanya menyatakan bahwa Eithers harus digunakan ketika kasus lain dari metode ini adalah "tidak luar biasa". Pertama, ini adalah definisi imho yang sangat, sangat kabur. Kedua, apakah itu sepadan dengan hukuman sintaksis? Maksudku, aku benar-benar tidak akan keberatan menggunakan Eithers jika bukan karena overhead sintaks yang mereka sajikan.
EitherSepertinya monad bagiku. Gunakan saat Anda membutuhkan manfaat komposisi fungsional yang disediakan monad. Atau mungkin juga tidak .
Eitherdengan sendirinya bukan monad. The proyeksi baik sisi kiri atau sisi kanan adalah monad, tapi Eitherdengan sendirinya tidak. Anda dapat menjadikannya monad, dengan "membiaskan" ke sisi kiri atau kanan. Namun, kemudian Anda memberikan semantik tertentu di kedua sisi Either. Scala Eitherawalnya tidak bias, tetapi bias agak baru-baru ini, sehingga saat ini, itu sebenarnya sebuah monad, tetapi "kebesaran" bukan properti yang melekat Eithermelainkan hasil dari itu menjadi bias.