Lựa chọn đầu tiên của tôi thường là sử dụng đệ quy. Nó chỉ nhỏ gọn hơn vừa phải, có khả năng nhanh hơn (chắc chắn không chậm hơn) và việc kết thúc sớm có thể làm cho logic rõ ràng hơn. Trong trường hợp này, bạn cần có các định nghĩa lồng nhau hơi khó xử:
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)
}
Lựa chọn thứ hai của tôi là sử dụng return
, vì nó giữ cho mọi thứ khác nguyên vẹn và bạn chỉ cần bọc nếp gấp def
để bạn có thứ gì đó để trả lại - trong trường hợp này, bạn đã có một phương thức, vì vậy:
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
mà trong trường hợp cụ thể này là nhỏ gọn hơn rất nhiều so với đệ quy (mặc dù chúng tôi đặc biệt không may mắn với đệ quy vì chúng tôi phải thực hiện một phép biến đổi có thể lặp lại / lặp). Luồng điều khiển nhảy vọt là điều cần tránh khi tất cả những thứ khác bằng nhau, nhưng ở đây thì không. Không có hại khi sử dụng nó trong những trường hợp nó có giá trị.
Nếu tôi làm việc này thường xuyên và muốn nó ở giữa một phương thức ở đâu đó (vì vậy tôi không thể chỉ sử dụng return), tôi có thể sẽ sử dụng xử lý ngoại lệ để tạo luồng điều khiển không cục bộ. Đó là, xét cho cùng, nó tốt ở điểm nào và việc xử lý lỗi không phải là lần duy nhất nó hữu ích. Mẹo duy nhất là tránh tạo ra một dấu vết ngăn xếp (thực sự chậm), và điều đó thật dễ dàng vì đặc điểm NoStackTrace
và đặc điểm con của nó ControlThrowable
đã làm điều đó cho bạn. Scala đã sử dụng nội bộ này (trên thực tế, đó là cách nó thực hiện việc trả về từ bên trong màn hình đầu tiên!). Hãy tạo của riêng chúng ta (không thể lồng vào nhau, mặc dù người ta có thể khắc phục điều đó):
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
})
}
Tất nhiên ở đây sử dụng return
sẽ tốt hơn, nhưng lưu ý rằng bạn có thể đặt ở shortcut
bất cứ đâu, không chỉ gói toàn bộ một phương thức.
Tiếp theo đối với tôi sẽ là triển khai lại màn hình đầu tiên (bản thân tôi hoặc tìm một thư viện thực hiện điều đó) để nó có thể báo hiệu kết thúc sớm. Hai cách tự nhiên để làm điều này là không truyền giá trị mà là Option
chứa giá trị, nơi None
biểu thị sự kết thúc; hoặc sử dụng chức năng chỉ báo thứ hai báo hiệu hoàn thành. Phần lười Scalaz do Kim Stebel thể hiện đã bao gồm trường hợp đầu tiên, vì vậy tôi sẽ hiển thị phần thứ hai (với cách triển khai có thể thay đổi):
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)(_ + _)
(Cho dù bạn thực hiện kết thúc bằng đệ quy, trả lại, lười biếng, v.v. hay không là tùy thuộc vào bạn.)
Tôi nghĩ rằng điều đó bao gồm các biến thể hợp lý chính; cũng có một số tùy chọn khác, nhưng tôi không chắc tại sao người ta lại sử dụng chúng trong trường hợp này. ( Iterator
Bản thân nó sẽ hoạt động tốt nếu nó có findOrPrevious
, nhưng nó không, và công việc bổ sung cần thiết để làm điều đó bằng tay khiến nó trở thành một lựa chọn ngớ ngẩn khi sử dụng ở đây.)