Độ trong suốt tham chiếu, được tham chiếu đến một hàm, chỉ ra rằng bạn có thể xác định kết quả của việc áp dụng hàm đó chỉ bằng cách xem xét các giá trị của các đối số của nó. Bạn có thể viết các hàm trong suốt tham chiếu trong bất kỳ ngôn ngữ lập trình nào, ví dụ Python, Scheme, Pascal, C.
Mặt khác, trong hầu hết các ngôn ngữ, bạn cũng có thể viết các hàm không tham chiếu trong suốt. Ví dụ: hàm Python này:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
không tham chiếu minh bạch, trong thực tế gọi
foo(x) + foo(x)
và
2 * foo(x)
sẽ tạo ra các giá trị khác nhau, cho bất kỳ đối số x
. Lý do cho điều này là hàm sử dụng và sửa đổi một biến toàn cục, do đó kết quả của mỗi lần gọi phụ thuộc vào trạng thái thay đổi này, và không chỉ dựa vào đối số của hàm.
Haskell, một ngôn ngữ chức năng thuần túy, tách biệt nghiêm ngặt việc đánh giá biểu thức trong đó các hàm thuần túy được áp dụng và luôn minh bạch, từ thực thi hành động (xử lý các giá trị đặc biệt), không minh bạch về mặt tham chiếu, tức là thực hiện cùng một hành động có thể có mỗi lần kết quả khác nhau.
Vì vậy, đối với bất kỳ chức năng Haskell
f :: Int -> Int
và bất kỳ số nguyên nào x
, nó luôn luôn đúng
2 * (f x) == (f x) + (f x)
Một ví dụ về một hành động là kết quả của chức năng thư viện getLine
:
getLine :: IO String
Kết quả của việc đánh giá biểu thức, hàm này (thực sự là một hằng số) trước hết tạo ra một giá trị thuần của kiểu IO String
. Các giá trị của loại này là các giá trị như bất kỳ giá trị nào khác: bạn có thể chuyển chúng xung quanh, đặt chúng vào cấu trúc dữ liệu, soạn chúng bằng các hàm đặc biệt, v.v. Ví dụ: bạn có thể tạo một danh sách các hành động như vậy:
[getLine, getLine] :: [IO String]
Các hành động đặc biệt ở chỗ bạn có thể yêu cầu thời gian chạy Haskell thực thi chúng bằng cách viết:
main = <some action>
Trong trường hợp này, khi chương trình Haskell của bạn được khởi động, bộ thực thi sẽ chuyển qua hành động bị ràng buộc main
và thực thi nó, có thể tạo ra các hiệu ứng phụ. Do đó, thực thi hành động không minh bạch về mặt tham chiếu vì thực hiện cùng một hành động hai lần có thể tạo ra các kết quả khác nhau tùy thuộc vào thời gian chạy làm đầu vào.
Nhờ hệ thống loại của Haskell, một hành động không bao giờ có thể được sử dụng trong bối cảnh mà loại khác được mong đợi và ngược lại. Vì vậy, nếu bạn muốn tìm độ dài của chuỗi, bạn có thể sử dụng length
hàm:
length "Hello"
sẽ trả về 5. Nhưng nếu bạn muốn tìm độ dài của chuỗi được đọc từ thiết bị đầu cuối, bạn không thể viết
length (getLine)
bởi vì bạn nhận được một lỗi loại: length
mong đợi một đầu vào của danh sách loại (và thực tế, một chuỗi là một danh sách) nhưng getLine
là một giá trị của loại IO String
(một hành động). Theo cách này, hệ thống loại đảm bảo rằng một giá trị hành động như getLine
(thực hiện được thực hiện bên ngoài ngôn ngữ cốt lõi và có thể không trong suốt tham chiếu) không thể được ẩn bên trong giá trị loại không hành động Int
.
CHỈNH SỬA
Để trả lời câu hỏi exizt, đây là một chương trình Haskell nhỏ đọc một dòng từ bảng điều khiển và in độ dài của nó.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
Hành động chính bao gồm hai giao dịch được thực hiện tuần tự:
getline
loại IO String
,
- cái thứ hai được xây dựng bằng cách đánh giá chức năng
putStrLn
của kiểu String -> IO ()
trên đối số của nó.
Chính xác hơn, hành động thứ hai được xây dựng bởi
- ràng buộc
line
với giá trị được đọc bởi hành động đầu tiên,
- đánh giá các hàm thuần túy
length
(tính độ dài dưới dạng số nguyên) và sau đó show
(biến số nguyên thành chuỗi),
- xây dựng hành động bằng cách áp dụng chức năng
putStrLn
cho kết quả của show
.
Tại thời điểm này, hành động thứ hai có thể được thực thi. Nếu bạn đã gõ "Xin chào", nó sẽ in "5".
Lưu ý rằng nếu bạn nhận được một giá trị từ một hành động bằng cách sử dụng <-
ký hiệu, bạn chỉ có thể sử dụng giá trị đó bên trong một hành động khác, ví dụ: bạn không thể viết:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
bởi vì show (length line)
có loại String
trong khi ký hiệu không yêu cầu một hành động ( getLine
loại IO String
) được theo sau bởi một hành động khác (ví dụ như putStrLn (show (length line))
loại IO ()
).
CHỈNH SỬA 2
Định nghĩa về tính minh bạch tham chiếu của Jörg W Mittag chung chung hơn của tôi (tôi đã nêu lên câu trả lời của anh ấy). Tôi đã sử dụng một định nghĩa hạn chế vì ví dụ trong câu hỏi tập trung vào giá trị trả về của các hàm và tôi muốn minh họa khía cạnh này. Tuy nhiên, RT nói chung đề cập đến ý nghĩa của toàn bộ chương trình, bao gồm các thay đổi về trạng thái toàn cầu và tương tác với môi trường (IO) gây ra bằng cách đánh giá một biểu thức. Vì vậy, để có một định nghĩa chung, chính xác, bạn nên tham khảo câu trả lời đó.