Belum lama ini saya mulai menggunakan Scala, bukan Java. Bagian dari proses "konversi" antara bahasa untuk saya adalah belajar menggunakan Either
s bukan (dicentang) Exception
s. 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 Either
telah berakhir Exception
adalah kinerja yang lebih baik; sebuah Exception
kebutuhan untuk membangun tumpukan-jejak dan sedang dilemparkan. Sejauh yang saya mengerti, melempar Exception
bukan bagian yang menuntut, tetapi membangun stack-trace adalah.
Tapi kemudian, kita selalu bisa membuat / mewarisi Exception
dengan scala.util.control.NoStackTrace
, dan lebih dari itu, saya melihat banyak kasus di mana sisi kiri sebuah Either
sebenarnya adalah Exception
(meneruskan peningkatan kinerja).
Satu keuntungan lain yang dimiliki Either
adalah 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- Exception
gaya 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 Either
atau try-catch
) cukup jelas dalam kedua kasus.
Jadi pertanyaan saya adalah - mengapa menggunakan Either
lebih 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" Exception
dengan Try
, atau menggunakan catch
untuk membungkus pengecualian Left
.
Masalah utama saya dengan Either
/ Try
muncul 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 Exception
menggunakan return
(yang sebenarnya adalah "tabu" di Scala). Kode masih lebih jelas daripada Either
pendekatan, dan sementara menjadi sedikit kurang bersih daripada Exception
gaya, 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 Left
penggantian throw new Exception
, dan metode implisit pada keduanya rightOrReturn
, adalah suplemen untuk propagasi pengecualian otomatis di stack.
Try
. Bagian tentang Either
vs Exception
hanya menyatakan bahwa Either
s 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 Either
s jika bukan karena overhead sintaks yang mereka sajikan.
Either
Sepertinya monad bagiku. Gunakan saat Anda membutuhkan manfaat komposisi fungsional yang disediakan monad. Atau mungkin juga tidak .
Either
dengan sendirinya bukan monad. The proyeksi baik sisi kiri atau sisi kanan adalah monad, tapi Either
dengan sendirinya tidak. Anda dapat menjadikannya monad, dengan "membiaskan" ke sisi kiri atau kanan. Namun, kemudian Anda memberikan semantik tertentu di kedua sisi Either
. Scala Either
awalnya tidak bias, tetapi bias agak baru-baru ini, sehingga saat ini, itu sebenarnya sebuah monad, tetapi "kebesaran" bukan properti yang melekat Either
melainkan hasil dari itu menjadi bias.