Làm thế nào là minh bạch tham chiếu được thi hành?


8

Trong các ngôn ngữ FP, việc gọi một hàm có cùng tham số lặp đi lặp lại sẽ trả lại cùng một kết quả (nghĩa là độ trong suốt tham chiếu).

Nhưng một chức năng như thế này (mã giả):

function f(a, b) {
    return a + b + currentDateTime.seconds;
}

sẽ không trả về cùng một kết quả cho cùng một tham số.

Làm thế nào những trường hợp này được xử lý trong FP?

Làm thế nào là minh bạch tham chiếu được thi hành? Hoặc là không và nó phụ thuộc vào các lập trình viên để hành xử chính họ?


5
Nó sẽ phụ thuộc vào ngôn ngữ, một số sẽ không thực thi tính minh bạch tham chiếu, một số sử dụng hệ thống loại để tách các chức năng minh bạch tham chiếu khỏi IO, ví dụ: Monads trong Haskell hoặc các loại duy nhất trong Clean
jk.

1
Một hệ thống loại tốt sẽ ngăn bạn gọi currentDateTimetừ f, bằng các ngôn ngữ thực thi tính minh bạch tham chiếu (như Haskell). Tôi sẽ để người khác cung cấp câu trả lời chi tiết hơn :) (gợi ý: currentDateTimecó IO và điều này sẽ hiển thị theo kiểu của nó)
Andres F.

Câu trả lời:


20

abNumbers, trong khi currentDateTime.secondstrả về một IO<Number>. Các loại đó không tương thích, bạn không thể thêm chúng lại với nhau, do đó chức năng của bạn không được gõ tốt và đơn giản là không được biên dịch. Ít nhất đó là cách nó được thực hiện bằng các ngôn ngữ thuần túy với một hệ thống kiểu tĩnh, như Haskell. Trong các ngôn ngữ không tinh khiết như ML, Scala hoặc F #, tùy thuộc vào lập trình viên để đảm bảo tính minh bạch tham chiếu và tất nhiên trong các ngôn ngữ được nhập động như Clojure hoặc Scheme, không có hệ thống loại tĩnh để thực thi tính minh bạch tham chiếu.


Vì vậy, không thể có hệ thống trình biên dịch / loại đảm bảo tính minh bạch tham chiếu cho tôi trong Scala như trong haskell?
cib 18/07/2015

8

Tôi sẽ cố gắng minh họa cách tiếp cận của Haskell (Tôi không chắc rằng trực giác của tôi là chính xác 100% vì tôi không phải là chuyên gia của Haskell, việc sửa chữa được hoan nghênh).

Mã của bạn có thể được viết bằng Haskell như sau:

import System.CPUTime

f :: Integer -> Integer -> IO Integer
f a b = do
          t <- getCPUTime
          return (a + b + (div t 1000000000000))

Vậy, minh bạch tham chiếu ở đâu? flà một hàm, được đưa ra hai số nguyên ab, sẽ tạo ra một hành động, như bạn có thể biết bằng kiểu trả về IO Integer. Hành động này sẽ luôn giống nhau, được đưa ra hai số nguyên, do đó, hàm ánh xạ một cặp số nguyên cho các hành động IO được minh bạch tham chiếu.

Khi hành động này được thực thi, giá trị nguyên mà nó tạo ra sẽ phụ thuộc vào thời gian CPU hiện tại: thực thi các hành động KHÔNG phải là ứng dụng chức năng.

Tóm tắt: Trong Haskell, bạn có thể sử dụng các hàm thuần túy để xây dựng và kết hợp các hành động phức tạp (giải trình tự, soạn thảo hành động, v.v.) theo cách minh bạch tham chiếu. Một lần nữa, lưu ý rằng trong ví dụ trên, hàm thuần fkhông trả về một số nguyên: nó trả về một hành động.

BIÊN TẬP

Một số chi tiết liên quan đến câu hỏi JohnDoDo.

Điều đó có nghĩa là "thực thi hành động KHÔNG phải là ứng dụng chức năng"?

Cho các tập hợp T1, T2, Tn, T, một hàm f là ánh xạ (quan hệ) liên kết với từng bộ trong T1 x T2 x ... x Tn một giá trị trong T. Vì vậy, ứng dụng hàm tạo ra một giá trị đầu ra cho một số giá trị đầu vào . Sử dụng cơ chế này, bạn có thể xây dựng các biểu thức đánh giá thành các giá trị, ví dụ giá trị 10là kết quả của việc đánh giá biểu thức 4 + 6. Lưu ý rằng, khi ánh xạ giá trị thành giá trị theo cách này, bạn không thực hiện bất kỳ loại đầu vào / đầu ra nào.

Trong Haskell, các hành động là các giá trị của các loại đặc biệt có thể được xây dựng bằng cách đánh giá các biểu thức có chứa các hàm thuần phù hợp hoạt động với các hành động. Theo cách này, chương trình Haskell là một hành động tổng hợp có được bằng cách đánh giá mainhàm. Hành động chính này có loại IO ().

Khi hành động tổng hợp này đã được xác định, một cơ chế khác (không phải ứng dụng chức năng) được sử dụng để gọi / thực hiện hành động (xem ví dụ ở đây ). Toàn bộ chương trình thực hiện là kết quả của việc gọi hành động chính có thể lần lượt gọi các hành động phụ. Cơ chế gọi này (có chi tiết nội bộ mà tôi không biết) đảm nhiệm việc thực hiện tất cả các cuộc gọi IO cần thiết, có thể truy cập vào thiết bị đầu cuối, đĩa, mạng, v.v.

Quay trở lại ví dụ. Hàm ftrên không trả về một số nguyên và bạn không thể viết hàm thực hiện IO và trả về một số nguyên cùng một lúc: bạn phải chọn một trong hai số nguyên.

Những gì bạn có thể làm là nhúng hành động được trả về f 2 3vào một hành động phức tạp hơn. Ví dụ: nếu bạn muốn in số nguyên được tạo bởi hành động đó, bạn có thể viết:

main :: IO ()
main = do
          x <- f 2 3
          putStrLn (show x)

Các doký hiệu chỉ ra rằng hành động trả về bởi các chức năng chính là thu được bằng một phần tuần tự của hai hành động nhỏ hơn, và các x <-ký hiệu chỉ ra rằng giá trị sản xuất trong hành động đầu tiên phải được thông qua với hành động thứ hai.

Trong hành động thứ hai

putStrLn (show x)

tên xđược liên kết với số nguyên được tạo ra bằng cách thực hiện hành động

f 2 3

Một điểm quan trọng là số nguyên được tạo ra khi hành động đầu tiên được gọi chỉ có thể sống bên trong các hành động IO: nó có thể được truyền từ một hành động IO sang hành động tiếp theo nhưng nó không thể được trích xuất dưới dạng giá trị số nguyên.

So sánh mainchức năng trên với cái này:

main = do
      let y = 2 + 3
      putStrLn (show y)

Trong trường hợp này, chỉ có một hành động, cụ thể putStrLn (show y), và ybị ràng buộc với kết quả của việc áp dụng hàm thuần túy +. Chúng ta cũng có thể định nghĩa hành động chính này như sau:

main = putStrLn "5"

Vì vậy, hãy chú ý cú pháp khác nhau

x <- f 2 3    -- Inject the value produced by an action into
              -- the following IO actions.
              -- The value may depend on when the action is
              -- actually executed. What happens when the action is
              -- executed is not known here: it may get user input,
              -- access the disk, the network, the system clock, etc.

let y = 2 + 3 -- Bind y to the result of applying the pure function `+`
              -- to the arguments 2 and 3.
              -- The value depends only on the arguments 2 and 3.

Tóm lược

  • Trong Haskell, các hàm thuần túy được sử dụng để xây dựng các hành động tạo thành một chương trình.
  • Hành động là giá trị của một loại đặc biệt.
  • Vì các hành động được xây dựng bằng cách áp dụng các hàm thuần túy, nên hành động xây dựng được minh bạch.
  • Sau khi một hành động đã được xây dựng, nó có thể được gọi bằng một cơ chế riêng.

2
Bạn có phiền chi tiết một chút executing actions is NOT function applicationcụm từ? Trong ví dụ của tôi, tôi có nghĩa là trả về một số nguyên. Điều gì xảy ra nếu trả về một số nguyên?
JohnDoDo

2
@JohnDoDo trong Haskell với sự lười biếng ít nhất (tôi không thể nói với bất kỳ ngôn ngữ minh bạch tham chiếu háo hức nào) cho đến khi nó thực sự phải được thực hiện. Điều này có nghĩa là trong ví dụ Giorgio cho thấy bạn đang thực hiện hành động đó và ngoài việc thực hiện những điều không đáng tin cậy, bạn không bao giờ có thể lấy số ra khỏi hành động IO, thay vào đó bạn phải kết hợp hành động đó với các hành động IO khác trong suốt chương trình của mình cho đến khi bạn kết thúc với Chính mà; bất ngờ bất ngờ là một hành động IO. Bản thân Haskell thực hiện hành động IO, nhưng trong suốt quá trình nó chỉ thực hiện các phần cần thiết và chỉ khi chúng tồn tại.
Jimmy Hoffa

@JohnDoDo Nếu bạn muốn trả về một số nguyên, thì fkhông thể có loại IO Integer(đó là một hành động, không phải là một số nguyên). Nhưng sau đó, nó không thể gọi là "ngày hiện tại", có loại IO Integer.
Andres F.

Ngoài ra, Số nguyên IO bạn nhận được khi đầu ra không thể được chuyển đổi trở lại thành Số nguyên thông thường và sau đó được sử dụng lại trong mã thuần. Về cơ bản, những gì diễn ra trong đơn vị IO vẫn ở trong đơn vị IO. (Có một ngoại lệ cho điều này, bạn có thể sử dụng unsafePerformIO để lấy lại giá trị, nhưng bằng cách đó, về cơ bản bạn đang nói với trình biên dịch rằng "Không sao, điều này thực sự minh bạch về mặt tham chiếu". Trình biên dịch sẽ tin bạn và lần tới khi bạn sử dụng hàm, nó có thể lấy giá trị của hàm được tính trước đó, thay vì sử dụng thời gian hiện tại.)
Michael Shaw

1
Tất cả các ví dụ cuối cùng của bạn cho thấy rằng bạn không có sẵn một ví dụ phù hợp Show. Nhưng bạn có thể dễ dàng thêm một, trong trường hợp đó mã sẽ biên dịch và chạy tốt. Không có gì đặc biệt về IOcác hành động liên quan đến show.

4

Cách tiếp cận thông thường là cho phép trình biên dịch theo dõi xem một hàm có thuần túy trong toàn bộ biểu đồ cuộc gọi hay không và từ chối mã khai báo các hàm là thuần túy làm những việc không trong sạch (trong đó "gọi hàm không tinh khiết" cũng là một điều không trong sạch).

Haskell thực hiện điều này bằng cách làm cho mọi thứ thuần túy trong ngôn ngữ; bất cứ điều gì không tinh khiết chạy trong thời gian chạy, không phải ngôn ngữ. Ngôn ngữ chỉ xây dựng các hành động IO bằng các hàm thuần túy. Thời gian chạy sau đó tìm thấy hàm thuần được gọi maintừ Mainmô-đun được chỉ định , đánh giá nó và thực hiện hành động kết quả (không tinh khiết).

Các ngôn ngữ khác thực dụng hơn về nó; một cách tiếp cận phổ biến là thêm cú pháp để đánh dấu các hàm 'thuần' và cấm mọi hành động không tinh khiết (cập nhật biến, gọi hàm không tinh khiết, cấu trúc I / O) bên trong các hàm đó.

Trong ví dụ của bạn, currentDateTimelà một hàm không tinh khiết (hoặc một cái gì đó hoạt động giống như một hàm), vì vậy việc gọi nó trong một khối thuần túy bị cấm và sẽ gây ra lỗi trình biên dịch. Trong Haskell, chức năng của bạn sẽ trông giống như thế này:

f :: Int -> Int -> IO Int
f a b = do
    ct <- getCurrentTime
    return (a + b + timeSeconds ct)

Nếu bạn đã cố gắng thực hiện điều này trong một chức năng không phải IO, như thế này:

f :: Int -> Int -> Int
f a b =
    let ct = getCurrentTime
    in a + b + timeSeconds ct

... Sau đó, trình biên dịch sẽ cho bạn biết rằng các loại của bạn không kiểm tra - getCurrentTimelà loại IO Time, không phải Time, nhưng timeSecondsmong đợi Time. Nói cách khác, Haskell tận dụng hệ thống loại của mình để mô hình hóa (và thực thi) độ tinh khiết.

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.