val-mutable so với var-immutable trong Scala


97

Có bất kỳ hướng dẫn nào trong Scala về thời điểm sử dụng val với bộ sưu tập có thể thay đổi so với việc sử dụng var với bộ sưu tập bất biến không? Hay bạn thực sự nên nhắm đến val với một bộ sưu tập bất biến?

Thực tế là có cả hai loại bộ sưu tập mang lại cho tôi rất nhiều sự lựa chọn, và thường tôi không biết làm thế nào để đưa ra lựa chọn đó.


Câu trả lời:


104

Câu hỏi khá phổ biến, câu hỏi này. Điều khó khăn là tìm các bản sao.

Bạn nên cố gắng cho sự minh bạch tham chiếu . Điều đó có nghĩa là, nếu tôi có một biểu thức "e", tôi có thể tạo một val x = evà thay thế ebằng x. Đây là thuộc tính mà khả năng đột biến bị phá vỡ. Bất cứ khi nào bạn cần đưa ra quyết định thiết kế, hãy tối đa hóa để có được sự minh bạch trong tham chiếu.

Như một vấn đề thực tế, một phương thức cục bộ varlà an toàn nhất vartồn tại, vì nó không thoát khỏi phương thức. Nếu phương pháp ngắn, thậm chí tốt hơn. Nếu không, hãy thử giảm nó bằng cách giải nén các phương pháp khác.

Mặt khác, một tập hợp có thể thay đổi có khả năng thoát ra, ngay cả khi nó không. Khi thay đổi mã, bạn có thể muốn chuyển nó sang các phương thức khác hoặc trả lại. Đó là loại thứ phá vỡ tính minh bạch của tham chiếu.

Trên một đối tượng (một lĩnh vực), điều tương tự cũng xảy ra khá nhiều, nhưng với hậu quả thảm khốc hơn. Dù bằng cách nào thì đối tượng sẽ có trạng thái và do đó, phá vỡ tính minh bạch tham chiếu. Nhưng có một bộ sưu tập có thể thay đổi có nghĩa là ngay cả bản thân đối tượng cũng có thể mất kiểm soát về việc ai đang thay đổi nó.


38
Nice, bức tranh lớn mới trong tâm trí tôi: thích immutable valhơn immutable varso với mutable valqua mutable var. Đặc biệt là immutable varhết mutable val!
Peter Schmitz,

2
Hãy nhớ rằng bạn vẫn có thể đóng (như đã rò rỉ một "chức năng" hiệu quả bên cạnh có thể thay đổi nó) trên một biến cục bộ var. Một tính năng thú vị khác của việc sử dụng bộ sưu tập bất biến là bạn có thể giữ các bản sao cũ xung quanh một cách hiệu quả, ngay cả khi các bản thay varđổi.
Mysterious Dan

1
tl; dr: thích var x: Set[Int] hơn val x: mutable.Set[Int] vì nếu bạn chuyển xsang một số hàm khác, trong trường hợp trước đây, bạn chắc chắn rằng, hàm đó không thể thay đổi xđối với bạn.
pathikrit

17

Nếu bạn làm việc với các tập hợp bất biến và bạn cần "sửa đổi" chúng, chẳng hạn như thêm các phần tử vào chúng trong một vòng lặp, thì bạn phải sử dụng vars vì bạn cần lưu trữ tập hợp kết quả ở đâu đó. Nếu bạn chỉ đọc từ các bộ sưu tập không thay đổi, thì hãy sử dụng vals.

Nói chung, hãy đảm bảo rằng bạn không nhầm lẫn giữa tham chiếu và đối tượng. vals là các tham chiếu bất biến (con trỏ hằng trong C). Đó là, khi bạn sử dụng val x = new MutableFoo(), bạn sẽ có thể thay đổi đối tượngxđiểm đến, nhưng bạn sẽ không thể thay đổi mà đối tượng x điểm. Ngược lại, nếu bạn sử dụng var x = new ImmutableFoo(). Lấy lời khuyên ban đầu của tôi: nếu bạn không cần thay đổi đối tượng nào một điểm tham chiếu, hãy sử dụng vals.


1
var immutable = something(); immutable = immutable.update(x)đánh bại mục đích sử dụng tập hợp bất biến. Bạn đã từ bỏ tính minh bạch tham chiếu và bạn thường có thể nhận được hiệu ứng tương tự từ một bộ sưu tập có thể thay đổi với độ phức tạp về thời gian tốt hơn. Trong bốn khả năng ( valvar, có thể thay đổi và bất biến), điều này có ý nghĩa nhất. Tôi thường sử dụng val mutable.
Jim Pivarski

3
@JimPivarski Tôi không đồng ý, cũng như những người khác, hãy xem câu trả lời của Daniel và nhận xét của Peter. Nếu bạn cần cập nhật cấu trúc dữ liệu, thì việc sử dụng var không thể thay đổi thay vì val có thể thay đổi có lợi thế là bạn có thể làm rò rỉ các tham chiếu đến cấu trúc với rủi ro rằng nó bị người khác sửa đổi theo cách phá vỡ các giả định cục bộ của bạn. Bất lợi cho những "người khác" này là họ có thể đọc dữ liệu cũ.
Malte Schwerhoff

Tôi đã thay đổi quyết định và tôi đồng ý với bạn (Tôi để lại nhận xét ban đầu của tôi cho lịch sử). Kể từ đó tôi đã sử dụng điều này, đặc biệt là trong var list: List[X] = Nil; list = item :: list; ...và tôi đã quên rằng tôi đã từng viết khác.
Jim Pivarski

@MalteSchwerhoff: "dữ liệu cũ" thực sự là mong muốn, tùy thuộc vào cách bạn thiết kế chương trình của mình, nếu tính nhất quán là quan trọng; ví dụ đây là một trong những nguyên tắc cơ bản chính về cách hoạt động của đồng thời trong Clojure.
Erik Kaplun

@ErikAllik Tôi sẽ không nói rằng dữ liệu cũ là mong muốn, nhưng tôi đồng ý rằng nó có thể hoàn toàn ổn, tùy thuộc vào sự đảm bảo mà bạn muốn / cần cung cấp cho khách hàng của mình. Hoặc bạn có một ví dụ trong đó việc đọc dữ liệu cũ thực sự là một lợi thế? Ý tôi không phải là hậu quả của việc chấp nhận dữ liệu cũ, đó có thể là hiệu suất tốt hơn hoặc một API đơn giản hơn.
Malte Schwerhoff

9

Cách tốt nhất để trả lời điều này là với một ví dụ. Giả sử chúng ta có một số quy trình chỉ đơn giản là thu thập các con số vì một lý do nào đó. Chúng tôi muốn ghi lại những con số này và sẽ gửi bộ sưu tập đến một quy trình khác để thực hiện việc này.

Tất nhiên, chúng tôi vẫn đang thu thập số liệu sau khi chúng tôi gửi bộ sưu tập đến bộ ghi. Và giả sử có một số chi phí trong quá trình ghi nhật ký làm chậm quá trình ghi nhật ký thực tế. Hy vọng rằng bạn có thể thấy điều này sẽ đi đến đâu.

Nếu chúng tôi lưu trữ bộ sưu tập này trong một bộ sưu tập có thể thay đổi val, (có thể thay đổi vì chúng tôi liên tục thêm vào nó), điều này có nghĩa là quá trình thực hiện ghi nhật ký sẽ xem xét cùng một đối tượng vẫn đang được cập nhật bởi quá trình thu thập của chúng tôi. Bộ sưu tập đó có thể được cập nhật bất kỳ lúc nào và vì vậy khi đến lúc ghi nhật ký, chúng tôi có thể không thực sự ghi nhật ký bộ sưu tập mà chúng tôi đã gửi.

Nếu chúng ta sử dụng một varcấu trúc bất biến , chúng ta sẽ gửi một cấu trúc dữ liệu không thể thay đổi đến bộ ghi. Khi chúng ta thêm số điện thoại hơn vào bộ sưu tập của chúng tôi, chúng tôi sẽ thay thế của chúng tôi varvới một cấu trúc dữ liệu không thay đổi mới . Điều này không có nghĩa là bộ sưu tập được gửi đến trình ghi nhật ký được thay thế! Nó vẫn đang tham chiếu đến bộ sưu tập mà nó đã được gửi. Vì vậy, trình ghi nhật ký của chúng tôi sẽ thực sự ghi lại bộ sưu tập mà nó nhận được.


1

Tôi nghĩ rằng các ví dụ trong bài đăng blog này sẽ làm sáng tỏ hơn, vì câu hỏi sử dụng kết hợp nào thậm chí còn trở nên quan trọng hơn trong các trường hợp đồng thời: tầm quan trọng của tính bất biến đối với tính đồng thời . Và trong khi chúng tôi đang ở đó, hãy lưu ý việc sử dụng ưu tiên đồng bộ hóa so với @volatile so với một cái gì đó như AtomicReference: ba công cụ


-2

var immutable so với val mutable

Ngoài nhiều câu trả lời xuất sắc cho câu hỏi này. Dưới đây là một ví dụ đơn giản, minh họa những nguy cơ tiềm ẩn của val mutable:

Các đối tượng có thể thay đổi có thể được sửa đổi bên trong các phương thức lấy chúng làm tham số, trong khi không được phép gán lại.

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

Kết quả:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

Điều gì đó như thế này không thể xảy ra với var immutablevì không được phép chuyển nhượng lại:

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

Kết quả trong:

error: reassignment to val

Vì các tham số hàm được coi vallà không được phép gán lại.


Điều này là không đúng. Lý do bạn gặp lỗi đó là vì bạn đã sử dụng Vector trong ví dụ thứ hai của mình, theo mặc định là không thay đổi được. Nếu bạn sử dụng ArrayBuffer, bạn sẽ thấy nó biên dịch tốt và làm điều tương tự khi nó chỉ thêm vào phần tử mới và in ra bộ đệm bị biến đổi. pastebin.com/vfq7ytaD
EdgeCaseBerg, 27/09/17

@EdgeCaseBerg, tôi đang cố ý sử dụng một Vectơ trong ví dụ thứ hai của mình, bởi vì tôi đang cố gắng chỉ ra rằng hành vi của ví dụ đầu tiên mutable vallà không thể với immutable var. Điều gì ở đây là không chính xác?
Akavall

Bạn đang so sánh táo với cam ở đây. Vector không có +=phương thức như bộ đệm mảng. Câu trả lời của bạn ngụ ý rằng +=nó giống như x = x + ynó không phải là. Tuyên bố của bạn rằng các tham số hàm được coi là vals là đúng và bạn nhận được lỗi mà bạn đề cập nhưng chỉ vì bạn đã sử dụng =. Bạn có thể gặp lỗi tương tự với ArrayBuffer, vì vậy khả năng thay đổi của các bộ sưu tập ở đây không thực sự phù hợp. Vì vậy, nó không phải là một câu trả lời tốt bởi vì nó không đạt được những gì OP đang nói về. Mặc dù đó là một ví dụ điển hình về sự nguy hiểm của việc chuyển một bộ sưu tập có thể thay đổi được nếu bạn không có ý định.
EdgeCaseBerg,

@EdgeCaseBerg Nhưng bạn không thể tái tạo hành vi mà tôi gặp phải ArrayBufferbằng cách sử dụng Vector. Câu hỏi của OP rất rộng, nhưng họ đang tìm kiếm gợi ý về thời điểm sử dụng cái nào, vì vậy tôi tin rằng câu trả lời của tôi hữu ích vì nó minh họa những mối nguy hiểm khi chuyển xung quanh bộ sưu tập có thể thay đổi (thực tế là valkhông giúp ích gì); immutable varlà an toàn hơn mutable val.
Akavall
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.