İlk tercihim genellikle özyinelemeyi kullanmak olacaktır. Yalnızca orta derecede daha az kompakttır, potansiyel olarak daha hızlıdır (kesinlikle daha yavaş değildir) ve erken sonlandırmada mantığı daha net hale getirebilir. Bu durumda, biraz garip olan iç içe geçmiş tanımlara ihtiyacınız vardır:
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)
}
İkinci seçimim return
, diğer her şeyi olduğu gibi tuttuğu için kullanmak olacaktır ve tek def
yapmanız gereken, geri dönecek bir şeyiniz olması için katlamayı sarmaktır - bu durumda, zaten bir yönteminiz var, yani:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
ki bu özel durumda özyinelemeden çok daha kompakttır (yinelenebilir / yineleyici bir dönüşüm yapmak zorunda olduğumuz için özellikle özyineleme ile şanssız kaldık). Sarsıntılı kontrol akışı, her şey eşit olduğunda kaçınılması gereken bir şeydir, ama burada değil. Değerli olduğu durumlarda kullanmaktan zarar gelmez.
Bunu sık sık yapıyor olsaydım ve bir yerde bir yöntemin ortasında isteseydim (bu yüzden sadece dönüşü kullanamazdım), muhtemelen yerel olmayan kontrol akışı oluşturmak için istisna işlemeyi kullanırdım. Sonuçta, iyi olduğu şeydir ve hata işleme, yararlı olduğu tek zaman değildir. Tek numara bir yığın izleme oluşturmaktan kaçınmaktır (ki bu gerçekten yavaştır) ve bu kolaydır çünkü özellik NoStackTrace
ve onun çocuk özelliği ControlThrowable
zaten bunu sizin için yapıyor. Scala bunu zaten dahili olarak kullanıyor (aslında, katın içinden dönüşü böyle gerçekleştiriyor!). Hadi kendimiz yapalım (iç içe geçirilemez, ancak biri bunu düzeltebilir):
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
})
}
Burada kullanmak elbette return
daha iyidir, ancak shortcut
sadece tüm bir yöntemi sarmakla kalmayıp, istediğiniz yere koyabileceğinizi unutmayın .
Benim için sonraki sırada, katlamayı yeniden uygulamak (ya kendim olsun ya da bunu yapan bir kitaplık bulmak), böylece erken sonlandırma sinyali verebilsin. Bunu yapmanın iki doğal yolu, değeri yaymak değil Option
, değeri içermektir, burada None
fesih anlamına gelir; veya tamamlandığını gösteren ikinci bir gösterge işlevi kullanmak için. Kim Stebel tarafından gösterilen Scalaz tembel katlanma zaten ilk vakayı kapsıyor, bu yüzden ikinciyi göstereceğim (değiştirilebilir bir uygulama ile):
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)(_ + _)
(Sonlandırmayı yineleme, dönüş, tembellik vb. İle uygulayıp uygulamayacağınız size bağlıdır.)
Sanırım bu ana makul değişkenleri kapsıyor; başka seçenekler de var, ancak bu durumda neden birinin bunları kullanacağından emin değilim. ( Iterator
A'ya sahip olsaydı kendisi iyi çalışırdı findOrPrevious
, ama olmadı ve bunu elle yapmak için gereken ekstra çalışma onu burada kullanmak için aptalca bir seçenek haline getiriyor.)