2014-09-10 15 views
12

Trong bộ sưu tập Scala, nếu ai muốn để lặp qua một bộ sưu tập (mà không trả lại kết quả, tức là làm một tác dụng phụ trên mọi phần tử của bộ sưu tập), nó có thể được thực hiện hoặc vớiScala: "map" vs "foreach" - có lý do nào để sử dụng "foreach" trong thực tế không?

final def foreach(f: (A) ⇒ Unit): Unit 

hoặc

final def map[B](f: (A) ⇒ B): SomeCollectionClass[B] 

với ngoại lệ của bản đồ có thể lười biếng (*), từ một góc độ người dùng cuối, tôi thấy không khác biệt trong các lời gọi:

myCollection.foreach { element => 
    doStuffWithElement(element); 
} 

myCollection.map { element => 
    doStuffWithElement(element); 
} 

cho rằng tôi chỉ có thể bỏ qua những gì kết quả đầu ra bản đồ. Tôi không thể nghĩ ra bất kỳ lý do cụ thể nào để sử dụng hai phương pháp khác nhau, khi map dường như bao gồm tất cả chức năng của foreach, và trên thực tế, tôi sẽ rất ấn tượng nếu một trình biên dịch thông minh & VM sẽ không tối ưu hóa việc tạo đối tượng bộ sưu tập đó vì nó không được gán cho bất kỳ thứ gì, hoặc đọc hoặc sử dụng ở bất kỳ đâu.

Vì vậy, câu hỏi là - tôi có đúng không - và không có lý do gì để gọi foreach ở bất kỳ đâu trong mã của người đó?

Ghi chú:

(*) Khái niệm bản đồ lười biếng, as throughly illustrated in this question, có thể thay đổi mọi thứ một chút và biện minh cho việc sử dụng của foreach, nhưng như xa như tôi có thể nhìn thấy, người ta đặc biệt cần phải vấp ngã khi một LazyMap, bình thường

(**) Nếu một không sử dụng một bộ sưu tập, nhưng viết một, sau đó người ta sẽ nhanh chóng vấp ngã khi thực tế là cú pháp cú pháp for hiểu là trong thực tế, một đường cú pháp mà tạo ra "Foreach" cuộc gọi, ví dụ: hai dòng sau tạo mã hoàn toàn tương đương:

for (element <- myCollection) { doStuffWithElement(element); } 
myCollection.foreach { element => doStuffWithElement(element); } 

Vì vậy, nếu ai quan tâm về những người khác sử dụng mà lớp bộ sưu tập với for cú pháp, người ta vẫn có thể muốn thực hiện foreach phương pháp.

+11

Thật tuyệt khi sử dụng 'foreach' thay vì' map' để phân biệt giữa các tác dụng phụ và các tác dụng không có tác dụng phụ. Tôi không quan tâm nếu trình biên dịch tối ưu hóa một cho người khác. Sự khác biệt làm cho mục đích của mã rõ ràng hơn. –

+1

Để tiến thêm một bước nữa thì LimbSoup, tôi thích dùng 'for ..' (sans yield) thay vì' .foreach' trực tiếp vì nó (cho tôi) cho thấy sự phân chia giữa lặp * cho * tác dụng phụ và chuyển đổi cho chuỗi. Ngoài ra, nó có thể nguy hiểm để giả định trình tự được lặp lại không phải là lười biếng. – user2864740

+0

Ngược lại với bạn, * I * sẽ rất ấn tượng nếu trình biên dịch Scala hiện tại và/hoặc JVM có thể tối ưu hóa 'bản đồ' thành mã' foreach' nếu kết quả không được sử dụng. Điều này xuất hiện với tôi như là một tối ưu hóa ** vô cùng nhỏ nhặt **. – ziggystar

Trả lời

10

tôi có thể nghĩ ra một vài động cơ:

  1. Khi foreach là dòng cuối cùng của một phương pháp mà là loại Unit trình biên dịch của bạn sẽ không đưa ra một lời cảnh báo nhưng sẽ với map (và bạn cần -Ywarn-value-discard trên). Đôi khi bạn nhận được warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses bằng cách sử dụng map nhưng không được với foreach.
  2. Khả năng đọc chung - người đọc có thể biết rằng bạn đang đột biến một số trạng thái mà không cần trả lại điều gì, nhưng cần phải hiểu rõ hơn về hoạt động tương tự nếu map được sử dụng
  3. Hơn nữa để 1. bạn cũng có thể có loại kiểm tra khi đi qua tên chức năng xung quanh, sau đó vào mapforeach
6
scala> (1 to 5).iterator map println 
res0: Iterator[Unit] = non-empty iterator 

scala> (1 to 5).iterator foreach println 
1 
2 
3 
4 
5 
+0

Đó là chính xác những gì tôi đã đề cập trong (*) - đó là lập bản đồ lười biếng, đó là mặc định trên tất cả các vòng lặp. – GreyCat

+0

Vì vậy, xác nhận của bạn trong câu hỏi này là 'foreach' và' map' giống nhau trừ khi chúng không? –

+0

Xác nhận của tôi là "chúng giống nhau trừ các trường hợp được nêu rõ ràng (*) và (**)", theo tôi, cả hai đều khá hiếm trong thực tế trung bình của người dùng cuối. – GreyCat

3

tôi muốn được ấn tượng nếu máy móc thiết bị xây dựng có thể được tối ưu hóa đi.

scala> :pa 
// Entering paste mode (ctrl-D to finish) 

implicit val cbf = new collection.generic.CanBuildFrom[List[Int],Int,List[Int]] { 
def apply() = new collection.mutable.Builder[Int, List[Int]] { 
val b = new collection.mutable.ListBuffer[Int] 
override def +=(i: Int) = { println(s"Adding $i") ; b +=(i) ; this } 
override def clear() =() ; override def result() = b.result() } 
def apply(from: List[Int]) = apply() } 

// Exiting paste mode, now interpreting. 

cbf: scala.collection.generic.CanBuildFrom[List[Int],Int,List[Int]] = [email protected] 

scala> List(1,2,3) map (_ + 1) 
Adding 2 
Adding 3 
Adding 4 
res1: List[Int] = List(2, 3, 4) 

scala> List(1,2,3) foreach (_ + 1) 
+0

Theo tôi có thể thấy, các công cụ tiềm ẩn liên quan đến CanBuildFrom' sẽ tạo ra một hệ thống phân cấp các lớp trên cấp JVM, bao gồm cả phương thức '+ =' thực sự làm gì đó ngoài việc xây dựng một biến. đi (và do đó tất cả các mã đó có thể được bỏ qua một cách an toàn). Tất nhiên, một phương thức * làm * một cái gì đó, đặc biệt là khi một cái gì đó là một cuộc gọi 'invokevirtual' rõ ràng, sẽ không được tối ưu hóa đi. – GreyCat

Các vấn đề liên quan