Với mục đích của câu trả lời này, tôi định nghĩa "ngôn ngữ chức năng thuần túy" có nghĩa là một ngôn ngữ chức năng trong đó các chức năng được minh bạch tham chiếu, tức là gọi cùng một chức năng nhiều lần với cùng một đối số sẽ luôn tạo ra kết quả giống nhau. Đây là, tôi tin rằng, định nghĩa thông thường của một ngôn ngữ hoàn toàn chức năng.
Các ngôn ngữ lập trình chức năng thuần túy không cho phép các tác dụng phụ (và do đó ít được sử dụng trong thực tế vì bất kỳ chương trình hữu ích nào cũng có tác dụng phụ, ví dụ như khi nó tương tác với thế giới bên ngoài).
Cách dễ nhất để đạt được tính minh bạch tham chiếu thực sự sẽ là không cho phép các tác dụng phụ và thực sự có các ngôn ngữ trong đó là trường hợp (chủ yếu là các miền cụ thể). Tuy nhiên, đây chắc chắn không phải là cách duy nhất và hầu hết các ngôn ngữ chức năng thuần túy (Haskell, Clean, ...) đều cho phép tác dụng phụ.
Ngoài ra, nói rằng ngôn ngữ lập trình không có tác dụng phụ trong sử dụng thực tế không thực sự công bằng - tôi chắc chắn không dành cho ngôn ngữ cụ thể của miền, nhưng ngay cả đối với các ngôn ngữ có mục đích chung, tôi tưởng tượng một ngôn ngữ có thể khá hữu ích mà không cung cấp tác dụng phụ . Có thể không dành cho các ứng dụng console, nhưng tôi nghĩ các ứng dụng GUI có thể được triển khai độc đáo mà không có tác dụng phụ trong mô hình phản ứng chức năng.
Về điểm 1, bạn có thể tương tác với môi trường bằng các ngôn ngữ chức năng thuần túy nhưng bạn phải đánh dấu rõ ràng mã (hàm) giới thiệu chúng (ví dụ: trong Haskell bằng các loại đơn âm).
Đó là một chút đơn giản hóa nó. Chỉ cần có một hệ thống trong đó các chức năng tác dụng phụ cần được đánh dấu như vậy (tương tự như tính chính xác trong C ++, nhưng với các tác dụng phụ chung) là không đủ để đảm bảo tính minh bạch tham chiếu. Bạn cần đảm bảo rằng một chương trình không bao giờ có thể gọi một hàm nhiều lần với cùng một đối số và nhận được các kết quả khác nhau. Bạn có thể làm điều đó bằng cách làm những thứ nhưreadLine
là một cái gì đó không phải là một chức năng (đó là những gì Haskell làm với đơn vị IO) hoặc bạn có thể không thể gọi các chức năng tác dụng phụ nhiều lần với cùng một đối số (đó là những gì Clean làm). Trong trường hợp sau, trình biên dịch sẽ đảm bảo rằng mỗi khi bạn gọi hàm có hiệu ứng phụ, bạn sẽ làm như vậy với một đối số mới và nó sẽ từ chối bất kỳ chương trình nào mà bạn chuyển cùng một đối số cho hàm hiệu ứng phụ hai lần.
Các ngôn ngữ lập trình chức năng thuần túy không cho phép viết một chương trình duy trì trạng thái (điều này làm cho việc lập trình trở nên rất khó xử vì trong nhiều ứng dụng bạn cần trạng thái).
Một lần nữa, một ngôn ngữ chức năng thuần túy rất có thể không cho phép trạng thái có thể thay đổi, nhưng chắc chắn nó có thể là thuần túy và vẫn có trạng thái có thể thay đổi, nếu bạn thực hiện nó theo cách tương tự như tôi đã mô tả với các tác dụng phụ ở trên. Trạng thái thực sự đột biến chỉ là một hình thức khác của tác dụng phụ.
Điều đó nói rằng, các ngôn ngữ lập trình chức năng chắc chắn không khuyến khích trạng thái đột biến - những ngôn ngữ thuần túy đặc biệt là như vậy. Và tôi không nghĩ rằng điều đó làm cho việc lập trình trở nên khó xử - hoàn toàn ngược lại. Đôi khi (nhưng không phải tất cả thường xuyên) trạng thái có thể thay đổi không thể tránh được mà không làm giảm hiệu suất hoặc sự rõ ràng (đó là lý do tại sao các ngôn ngữ như Haskell có cơ sở cho trạng thái có thể thay đổi), nhưng thường thì có thể.
Nếu họ là những quan niệm sai lầm, làm thế nào họ đến?
Tôi nghĩ rằng nhiều người chỉ đơn giản đọc "một hàm phải tạo ra cùng một kết quả khi được gọi với cùng một đối số" và kết luận rằng không thể thực hiện một cái gì đó giống như readLine
hoặc mã duy trì trạng thái có thể thay đổi. Vì vậy, họ chỉ đơn giản là không nhận thức được "mánh gian lận" mà các ngôn ngữ chức năng thuần túy có thể sử dụng để giới thiệu những điều này mà không phá vỡ tính minh bạch tham chiếu.
Ngoài ra, trạng thái có thể thay đổi được khuyến khích rất nhiều trong các ngôn ngữ chức năng, do đó, sẽ không có nhiều bước nhảy vọt khi cho rằng nó hoàn toàn không được phép trong các ngôn ngữ hoàn toàn chức năng.
Bạn có thể viết một đoạn mã (có thể nhỏ) minh họa cách thành ngữ Haskell để (1) thực hiện các tác dụng phụ và (2) thực hiện tính toán với trạng thái không?
Đây là một ứng dụng trong Pseudo-Haskell yêu cầu người dùng đặt tên và chào đón anh ta. Pseudo-Haskell là ngôn ngữ mà tôi vừa phát minh ra, có hệ thống IO của Haskell, nhưng sử dụng cú pháp thông thường hơn, tên hàm mô tả nhiều hơn và không có do
chú thích (vì điều đó sẽ làm sao lãng chính xác hoạt động của đơn vị IO):
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
Manh mối ở đây là readLine
một giá trị của kiểu IO<String>
và composeMonad
là một hàm lấy một đối số của kiểu IO<T>
(đối với một số loại T
) và một đối số khác là một hàm lấy một đối số của kiểu T
và trả về một giá trị của kiểu IO<U>
(đối với một số loại U
). print
là một hàm lấy một chuỗi và trả về giá trị của kiểu IO<void>
.
Giá trị của loại IO<A>
là một giá trị "mã hóa" một hành động nhất định tạo ra giá trị của loại A
. composeMonad(m, f)
tạo ra một IO
giá trị mới mã hóa hành động m
tiếp theo là hành động của f(x)
, trong đó x
giá trị được tạo ra bằng cách thực hiện hành động của m
.
Trạng thái có thể thay đổi sẽ trông như thế này:
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
Đây mutableVariable
là một hàm lấy giá trị của bất kỳ loại nào T
và tạo ra một MutableVariable<T>
. Hàm getValue
lấy MutableVariable
và trả về một IO<T>
giá trị tạo ra giá trị hiện tại của nó. setValue
lấy a MutableVariable<T>
và a T
và trả về một IO<void>
giá trị đặt. composeVoidMonad
cũng giống như composeMonad
ngoại trừ rằng đối số thứ nhất là một đối số IO
không tạo ra giá trị hợp lý và đối số thứ hai là một đơn vị khác, không phải là một hàm trả về một đơn nguyên.
Trong Haskell có một số đường cú pháp, làm cho toàn bộ thử thách này bớt đau đớn, nhưng rõ ràng trạng thái có thể thay đổi là điều mà ngôn ngữ không thực sự muốn bạn làm.