Pilihan pertama saya biasanya menggunakan rekursi. Ini hanya agak kurang kompak, berpotensi lebih cepat (tentu tidak lebih lambat), dan di penghentian awal dapat membuat logika lebih jelas. Dalam hal ini Anda memerlukan def bersarang yang agak canggung:
def sumEvenNumbers(nums: Iterable[Int]) = {
def sumEven(it: Iterator[Int], n: Int): Option[Int] = {
if (it.hasNext) {
val x = it.next
if ((x % 2) == 0) sumEven(it, n+x) else None
}
else Some(n)
}
sumEven(nums.iterator, 0)
}
Pilihan kedua saya adalah menggunakan return
, karena itu membuat semua yang lain tetap utuh dan Anda hanya perlu membungkus lipatan def
sehingga Anda memiliki sesuatu untuk dikembalikan - dalam hal ini, Anda sudah memiliki metode, jadi:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
yang dalam kasus khusus ini jauh lebih kompak daripada rekursi (meskipun kami sangat tidak beruntung dengan rekursi karena kami harus melakukan transformasi iterable / iterator). Aliran kontrol gelisah adalah sesuatu yang harus dihindari ketika semuanya sama, tetapi di sini tidak. Tidak ada salahnya menggunakannya jika itu berharga.
Jika saya sering melakukan ini dan menginginkannya di tengah-tengah metode di suatu tempat (jadi saya tidak bisa hanya menggunakan pengembalian), saya mungkin akan menggunakan penanganan pengecualian untuk menghasilkan aliran kontrol non-lokal. Bagaimanapun, itu adalah keahliannya, dan penanganan kesalahan bukan satu-satunya saat berguna. Satu-satunya trik adalah dengan menghindari pembuatan jejak tumpukan (yang sangat lambat), dan itu mudah karena sifat NoStackTrace
dan sifat turunannya ControlThrowable
sudah melakukannya untuk Anda. Scala sudah menggunakan ini secara internal (sebenarnya, begitulah cara mengimplementasikan pengembalian dari dalam flip!). Mari buat milik kita sendiri (tidak dapat disarangkan, meskipun seseorang dapat memperbaikinya):
import scala.util.control.ControlThrowable
case class Returned[A](value: A) extends ControlThrowable {}
def shortcut[A](a: => A) = try { a } catch { case Returned(v) => v }
def sumEvenNumbers(nums: Iterable[Int]) = shortcut{
Option(nums.foldLeft(0){ (n,x) =>
if ((x % 2) != 0) throw Returned(None)
n+x
})
}
Di sini tentu saja menggunakan return
lebih baik, tetapi perhatikan bahwa Anda bisa meletakkannya di shortcut
mana saja, tidak hanya membungkus seluruh metode.
Baris berikutnya bagi saya adalah menerapkan ulang lipatan (baik saya sendiri atau untuk menemukan perpustakaan yang melakukannya) sehingga dapat menandakan penghentian lebih awal. Dua cara alami untuk melakukan ini adalah dengan tidak menyebarkan nilai tetapi Option
mengandung nilai, di mana None
menandakan penghentian; atau untuk menggunakan fungsi indikator kedua yang menandakan penyelesaian. Lazy fold Scalaz yang ditunjukkan oleh Kim Stebel sudah mencakup kasus pertama, jadi saya akan menunjukkan yang kedua (dengan implementasi yang bisa berubah):
def foldOrFail[A,B](it: Iterable[A])(zero: B)(fail: A => Boolean)(f: (B,A) => B): Option[B] = {
val ii = it.iterator
var b = zero
while (ii.hasNext) {
val x = ii.next
if (fail(x)) return None
b = f(b,x)
}
Some(b)
}
def sumEvenNumbers(nums: Iterable[Int]) = foldOrFail(nums)(0)(_ % 2 != 0)(_ + _)
(Apakah Anda menerapkan penghentian dengan rekursi, pengembalian, kemalasan, dll. Terserah Anda.)
Saya pikir itu mencakup varian masuk akal utama; ada beberapa opsi lain juga, tetapi saya tidak yakin mengapa seseorang akan menggunakannya dalam kasus ini. ( Iterator
itu sendiri akan bekerja dengan baik jika memiliki findOrPrevious
, tetapi tidak, dan kerja ekstra yang diperlukan untuk melakukannya dengan tangan menjadikannya pilihan konyol untuk digunakan di sini.)