Tôi chỉ muốn chỉ ra rằng các kiểu dữ liệu và đối sánh mẫu (tính xấp xỉ đầu tiên) chỉ là tính năng ngôn ngữ hữu ích nhưng dư thừa, có thể được triển khai hoàn toàn bằng cách sử dụng phép tính lambda. Nếu bạn hiểu cách triển khai chúng trong phép tính lambda, bạn có thể hiểu tại sao chúng không cần Eq
để thực hiện khớp mẫu.
Thực hiện các loại dữ liệu trong phép tính lambda được gọi là "Giáo hóa mã hóa" chúng (sau khi Alonzo Church, người đã chứng minh làm thế nào để làm điều này). Các chức năng được mã hóa của Giáo hội còn được gọi là "Kiểu chuyển tiếp liên tục".
Nó được gọi là "kiểu chuyển tiếp liên tục" bởi vì thay vì cung cấp giá trị, bạn cung cấp một hàm sẽ xử lý giá trị. Vì vậy, ví dụ, thay vì đưa ra một người dùng một Int
, tôi có thể thay vì cung cấp cho họ một giá trị của các loại sau đây:
type IndirectInt = forall x . (Int -> x) -> x
Loại trên là "đẳng cấu" một Int
. "Đẳng cấu" chỉ là một cách ưa thích để nói rằng chúng ta có thể chuyển đổi bất kỳ IndirectInt
thành một Int
:
fw :: IndirectInt -> Int
fw indirect = indirect id
... và chúng ta có thể chuyển đổi bất kỳ Int
thành một IndirectInt
:
bw :: Int -> IndirectInt
bw int = \f -> f int
... sao cho:
fw . bw = id -- Exercise: Prove this
bw . fw = id -- Exercise: Prove this
Sử dụng kiểu chuyển tiếp liên tục, chúng tôi có thể chuyển đổi bất kỳ loại dữ liệu nào thành thuật ngữ lambda-calculus.Hãy bắt đầu với một kiểu đơn giản như:
data Either a b = Left a | Right b
Trong phong cách tiếp-đi qua, điều này sẽ trở thành:
type IndirectEither a b = forall x . (Either a b -> x) -> x
Nhưng Alonzo Church là thông minh và nhận thấy rằng đối với bất kỳ loại với nhiều nhà xây dựng, chúng ta có thể chỉ cung cấp một hàm riêng biệt cho mỗi hàm tạo. Vì vậy, trong trường hợp của các loại trên, thay vì cung cấp một chức năng của loại (Either a b-> x)
, chúng tôi thay vì có thể cung cấp hai chức năng riêng biệt, một cho a
, và một cho b
, và đó sẽ là chỉ là tốt:
type IndirectEither a b = forall x . (a -> x) -> (b -> x) -> x
-- Exercise: Prove that this definition is isomorphic to the previous one
Điều gì về một loại như Bool
nơi các nhà thầu không có đối số? Vâng, Bool
là đẳng cấu với Either()()
(Tập thể dục: Chứng minh điều này), vì vậy chúng tôi chỉ có thể mã hóa nó như:
type IndirectBool = forall x . (() -> x) -> (() -> x) -> x
Và () -> x
chỉ là đẳng cấu với x
(Tập thể dục: Chứng minh điều này), vì vậy chúng tôi có thể tiếp tục viết lại nó như là :
type IndirectBool = forall x . x -> x -> x
chỉ có hai chức năng mà có thể có các loại trên:
true :: a -> a -> a
true a _ = a
false :: a -> a -> a
false _ a = a
Do i định hình, chúng ta có thể đảm bảo rằng tất cả các bảng mã của Giáo hội sẽ có nhiều triển khai vì có thể có giá trị của kiểu dữ liệu gốc, vì vậy không có trùng hợp ngẫu nhiên rằng có hai hàm chính xác là IndirectBool
, giống như có hai nhà xây dựng chính xác cho Bool
.
Nhưng làm cách nào để chúng tôi khớp mẫu trên IndirectBool
của chúng tôi? Ví dụ, với một bình thường Bool
, chúng ta có thể chỉ cần viết:
expression1 :: a
expression2 :: a
case someBool of
True -> expression1
False -> expression2
Vâng, với chúng tôi IndirectBool
nó đã đi kèm với những công cụ để giải cấu trúc riêng của mình. Chúng tôi sẽ chỉ cần viết:
myIndirectBool expression1 expression2
Chú ý rằng nếu myIndirectBool
là true
, nó sẽ chọn các biểu hiện đầu tiên, và nếu nó là false
nó sẽ lấy biểu thức thứ hai, cũng giống như khi chúng tôi đã bằng cách nào đó mô hình kết hợp trên giá trị của nó .
Hãy thử làm điều tương tự với số IndirectEither
. Sử dụng một bình thường Either
, chúng tôi muốn viết:
f :: a -> c
g :: b -> c
case someEither of
Left a -> f a
Right b -> g b
Với IndirectEither
, chúng tôi chỉ cần viết:
someIndirectEither f g
Nói tóm lại, khi chúng ta viết các loại trong phong cách tiếp-đi qua, các continuations là giống như các câu lệnh case của một trường hợp xây dựng, vì vậy tất cả những gì chúng ta đang làm là chuyển qua từng câu lệnh case khác nhau như các đối số cho hàm.
Đây là lý do bạn không cần bất kỳ ý nghĩa nào về số Eq
đối với kiểu đối sánh trên một loại. Trong phép tính lambda, kiểu quyết định cái gì là "bằng" chỉ đơn giản bằng cách xác định đối số nào mà nó chọn ra từ đối số được cung cấp cho nó.Vì vậy, nếu tôi là một true
, tôi chứng minh tôi "bằng" với true
bằng cách luôn chọn đối số đầu tiên của tôi. Nếu tôi là false
, tôi chứng minh rằng tôi "bằng" thành false
bằng cách luôn chọn đối số thứ hai của mình. Trong ngắn hạn, hàm tạo "bình đẳng" tóm tắt thành "đẳng thức vị trí", luôn luôn được định nghĩa trong phép tính lambda, và nếu chúng ta có thể phân biệt một tham số là "đầu tiên" và khác là "thứ hai", đó là tất cả những gì chúng ta cần khả năng "so sánh" các nhà xây dựng.
Tôi không mong đợi sẽ chạm vào chi tiết này khi tôi đang tìm kiếm câu hỏi của mình, cảm ơn vì đã chứng ngộ. Tôi có nhiều hơn một "aha" khi đọc nó. Đặc biệt là các định nghĩa của 'true' và' false' cho đến khi tôi thấy ví dụ 6 dòng dưới đây làm cho nó hoàn toàn rõ ràng. – epsilonhalbe
Mặc dù ban đầu nó có vẻ là lý thuyết và được tìm kiếm nhiều, nhưng kiểu mẫu CPS kiểu này thực sự xuất hiện thường xuyên trong thực tế. Ví dụ, Smalltalk không có câu lệnh if, nhưng boolean có các phương thức nhận callback cho true và cho trường hợp false. – hugomg