Câu hỏi này yêu cầu một số câu trả lời đặc biệt khó tính, vì đó là về cách xác định từ vựng chung.
Đầu tiên, hàm là một loại quan hệ toán học giữa "miền" của đầu vào và "phạm vi" (hoặc tên miền) của đầu ra. Mỗi đầu vào tạo ra một đầu ra rõ ràng. Ví dụ, hàm bổ sung số nguyên +
chấp nhận đầu vào trong miền Int x Int
và tạo kết quả đầu ra trong phạm vi Int
.
object Ex0 {
def +(x: Int, y: Int): Int = x + y
}
Với bất kỳ giá trị cho x
và y
, rõ ràng +
sẽ luôn tạo ra kết quả tương tự. Đây là một hàm. Nếu trình biên dịch thông minh hơn, nó có thể chèn mã để lưu vào bộ nhớ cache kết quả của hàm này cho mỗi cặp đầu vào và thực hiện tra cứu bộ nhớ cache làm tối ưu hóa. Rõ ràng là an toàn ở đây.
Vấn đề là trong phần mềm, thuật ngữ "hàm" đã phần nào bị lạm dụng: mặc dù hàm chấp nhận đối số và trả về giá trị như được khai báo trong chữ ký của chúng, chúng cũng có thể đọc và ghi vào một số ngữ cảnh bên ngoài. Ví dụ:
class Ex1 {
def +(x: Int): Int = x + Random.nextInt
}
Chúng tôi không thể nghĩ về điều này như một hàm toán học nữa, bởi vì đối với một giá trị nhất định của x
, +
có thể tạo ra kết quả khác nhau (tùy thuộc vào một giá trị ngẫu nhiên, mà không xuất hiện bất cứ nơi nào trong +
's chữ ký). Kết quả của +
không thể được lưu trữ an toàn như được mô tả ở trên. Vì vậy, bây giờ chúng tôi có một vấn đề từ vựng, mà chúng tôi giải quyết bằng cách nói rằng Ex0.+
là tinh khiết và Ex1.+
thì không.
Được rồi, vì hiện tại chúng tôi đã chấp nhận một mức độ tạp chất nào đó, chúng tôi cần xác định loại tạp chất nào mà chúng tôi đang nói đến! Trong trường hợp này, chúng tôi đã nói sự khác biệt là chúng ta có thể cache Ex0.+
'kết quả của kết hợp với đầu vào của nó x
và y
, và rằng chúng ta không thể bộ nhớ cache Ex1.+
' s kết quả liên quan đến đầu vào của nó x
. Thuật ngữ chúng tôi sử dụng để mô tả khả năng lưu trữ (hoặc, đúng hơn, khả năng thay thế của một cuộc gọi hàm với đầu ra của nó) là tính minh bạch tham chiếu.
Tất cả các hàm thuần túy đều có tính minh bạch tham chiếu, nhưng một số hàm trong suốt tham chiếu không thuần túy. Ví dụ:
object Ex2 {
var lastResult: Int
def +(x: Int, y: Int): Int = {
lastResult = x + y
lastResult
}
}
Ở đây chúng ta không đọc từ bất kỳ bối cảnh bên ngoài, và giá trị sản xuất bởi Ex2.+
cho bất kỳ đầu vào x
và y
sẽ luôn là khả năng lưu nhớ, như trong Ex0
. Đây là tham chiếu trong suốt, nhưng nó có hiệu ứng phụ , để lưu trữ giá trị cuối cùng được tính theo hàm. Người khác có thể đến sau và lấy lastResult
, điều này sẽ cung cấp cho họ một số thông tin chi tiết lén lút về những gì đang xảy ra với Ex2.+
!
Một mặt lưu ý: bạn cũng có thể tranh luận rằng Ex2.+
là không referentially minh bạch, bởi vì mặc dù bộ nhớ đệm là an toàn đối với kết quả của chức năng với, tác dụng phụ là âm thầm bỏ qua trong trường hợp của một bộ nhớ cache " đánh." Nói cách khác, việc giới thiệu bộ nhớ cache thay đổi ý nghĩa của chương trình, nếu tác dụng phụ là quan trọng (do đó, Norman Ramsey's comment)! Nếu bạn thích định nghĩa này, thì hàm phải thuần khiết để có tính minh bạch trong suốt.
Bây giờ, một điều cần lưu ý ở đây là nếu chúng tôi gọi Ex2.+
hai lần trở lên liên tiếp với cùng một yếu tố đầu vào, lastResult
sẽ không thay đổi. Tác dụng phụ của việc gọi phương thức n lần tương đương với tác dụng phụ của việc chỉ gọi phương thức một lần và vì vậy chúng tôi nói rằng Ex2.+
là idempotent. Chúng tôi có thể thay đổi:
object Ex3 {
var history: Seq[Int]
def +(x: Int, y: Int): Int = {
result = x + y
history = history :+ result
result
}
}
Hiện tại, mỗi lần chúng tôi gọi Ex3.+
, thay đổi lịch sử, do đó chức năng không còn là không có giá trị.
Được rồi, bản tóm tắt cho đến thời điểm này: hàm thuần túy là chức năng không đọc hoặc ghi vào bất kỳ ngữ cảnh bên ngoài nào. Đó là cả hai có liên quan trong suốt và tác dụng phụ miễn phí. Một hàm đọc từ một số ngữ cảnh bên ngoài không còn minh bạch tham chiếu nữa, trong khi một hàm viết cho một số ngữ cảnh bên ngoài không còn miễn phí các tác dụng phụ nữa. Và cuối cùng, một hàm mà khi được gọi nhiều lần với cùng một đầu vào có cùng tác dụng phụ khi gọi nó chỉ một lần, được gọi là idempotent. Lưu ý rằng một hàm không có tác dụng phụ, chẳng hạn như hàm thuần túy, cũng là không có giá trị!
Vì vậy, làm thế nào để khả năng biến đổi và bất biến phát tất cả điều này? Vâng, nhìn lại Ex2
và Ex3
. Họ giới thiệu các biến thể var
s. Các tác dụng phụ của Ex2.+
và Ex3.+
là để biến đổi var
s tương ứng của chúng! Vì vậy, khả năng biến đổi và tác dụng phụ đi đôi với nhau; một hàm chỉ hoạt động trên dữ liệu bất biến phải là tác dụng phụ miễn phí. Nó có thể vẫn không thuần khiết (có nghĩa là, nó có thể không được tham chiếu trong suốt), nhưng ít nhất nó sẽ không tạo ra các tác dụng phụ.
Câu hỏi tiếp theo hợp lý cho câu hỏi này có thể là: "lợi ích của phong cách chức năng thuần túy là gì?" Câu trả lời cho câu hỏi đó có liên quan nhiều hơn;)
bạn quên "idempotence" cũng: P – mergeconflict