Từ trước đến nay, thuật ngữ mọi người sử dụng không hoàn toàn nhất quán. Có rất nhiều khái niệm lấy cảm hứng từ monads-nhưng-nói-đúng-không-hoàn-toàn. Thuật ngữ "đơn nguyên được lập chỉ mục" là một trong số (bao gồm "đơn nguyên" và "đơn nguyên được tham số hóa" (tên của Atkey cho chúng)) các thuật ngữ được sử dụng để mô tả một khái niệm như vậy. (Một khái niệm khác như vậy, nếu bạn quan tâm, là "đơn nguyên hiệu ứng tham số" của Katsumata, được lập chỉ mục bởi một monoid, trong đó lợi nhuận được lập chỉ mục trung lập và ràng buộc được tích lũy trong chỉ mục của nó.)
Trước hết, hãy kiểm tra các loại.
IxMonad (m :: state -> state -> * -> *)
Đó là, loại "tính toán" (hoặc "hành động", nếu bạn thích, nhưng tôi sẽ gắn bó với "tính toán"), trông giống như
m before after value
ở đâu before, after :: state
và value :: *
. Ý tưởng là nắm bắt các phương tiện để tương tác một cách an toàn với một hệ thống bên ngoài có một số khái niệm về trạng thái có thể đoán trước được . Kiểu của một máy tính cho bạn biết trạng thái before
mà nó phải chạy, trạng thái sẽ after
chạy và (giống như với các đơn nguyên thông thường *
) mà máy value
tính tạo ra kiểu gì.
Các bit và mảnh thông thường - *
tương tự như một đơn nguyên và state
- tương tự như chơi domino.
ireturn :: a -> m i i a -- returning a pure value preserves state
ibind :: m i j a -> -- we can go from i to j and get an a, thence
(a -> m j k b) -- we can go from j to k and get a b, therefore
-> m i k b -- we can indeed go from i to k and get a b
Khái niệm "mũi tên Kleisli" (hàm mang lại khả năng tính toán) do đó được tạo ra là
a -> m i j b -- values a in, b out; state transition i to j
và chúng tôi nhận được một bố cục
icomp :: IxMonad m => (b -> m j k c) -> (a -> m i j b) -> a -> m i k c
icomp f g = \ a -> ibind (g a) f
và, luật pháp đảm bảo chính xác điều đó ireturn
và icomp
cung cấp cho chúng tôi một danh mục
ireturn `icomp` g = g
f `icomp` ireturn = f
(f `icomp` g) `icomp` h = f `icomp` (g `icomp` h)
hoặc, trong C / Java giả mạo hài hước / bất cứ điều gì,
g(); skip = g()
skip; f() = f()
{g(); h()}; f() = h(); {g(); f()}
Quan tâm làm gì? Để mô hình hóa "quy tắc" của sự tương tác. Ví dụ: bạn không thể đẩy đĩa dvd ra nếu không có một đĩa nào trong ổ đĩa và bạn không thể đưa một đĩa dvd vào ổ đĩa nếu đã có một đĩa đệm trong đó. Vì thế
data DVDDrive :: Bool -> Bool -> * -> * where -- Bool is "drive full?"
DReturn :: a -> DVDDrive i i a
DInsert :: DVD -> -- you have a DVD
DVDDrive True k a -> -- you know how to continue full
DVDDrive False k a -- so you can insert from empty
DEject :: (DVD -> -- once you receive a DVD
DVDDrive False k a) -> -- you know how to continue empty
DVDDrive True k a -- so you can eject when full
instance IxMonad DVDDrive where -- put these methods where they need to go
ireturn = DReturn -- so this goes somewhere else
ibind (DReturn a) k = k a
ibind (DInsert dvd j) k = DInsert dvd (ibind j k)
ibind (DEject j) k = DEject j $ \ dvd -> ibind (j dvd) k
Với điều này tại chỗ, chúng ta có thể xác định các lệnh "nguyên thủy"
dInsert :: DVD -> DVDDrive False True ()
dInsert dvd = DInsert dvd $ DReturn ()
dEject :: DVDrive True False DVD
dEject = DEject $ \ dvd -> DReturn dvd
từ đó những người khác được lắp ráp với ireturn
và ibind
. Bây giờ, tôi có thể viết (mượn- do
chú thích)
discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dvd' <- dEject; dInsert dvd ; ireturn dvd'
nhưng không phải là không thể
discSwap :: DVD -> DVDDrive True True DVD
discSwap dvd = do dInsert dvd; dEject -- ouch!
Ngoài ra, người ta có thể xác định trực tiếp các lệnh nguyên thủy của một người
data DVDCommand :: Bool -> Bool -> * -> * where
InsertC :: DVD -> DVDCommand False True ()
EjectC :: DVDCommand True False DVD
và sau đó khởi tạo mẫu chung
data CommandIxMonad :: (state -> state -> * -> *) ->
state -> state -> * -> * where
CReturn :: a -> CommandIxMonad c i i a
(:?) :: c i j a -> (a -> CommandIxMonad c j k b) ->
CommandIxMonad c i k b
instance IxMonad (CommandIxMonad c) where
ireturn = CReturn
ibind (CReturn a) k = k a
ibind (c :? j) k = c :? \ a -> ibind (j a) k
Trên thực tế, chúng tôi đã nói các mũi tên Kleisli nguyên thủy là gì ("domino" là gì), sau đó xây dựng một khái niệm phù hợp về "chuỗi tính toán" cho chúng.
Lưu ý rằng đối với mọi đơn nguyên được lập chỉ mục m
, "đường chéo không thay đổi" m i i
là một đơn nguyên, nhưng nói chung, m i j
không phải. Hơn nữa, các giá trị không được lập chỉ mục nhưng các tính toán được lập chỉ mục, vì vậy một đơn nguyên được lập chỉ mục không chỉ là ý tưởng thông thường của đơn nguyên được khởi tạo cho một số danh mục khác.
Bây giờ, hãy nhìn lại loại mũi tên Kleisli
a -> m i j b
Chúng tôi biết rằng chúng tôi phải ở trạng thái i
để bắt đầu và chúng tôi dự đoán rằng bất kỳ sự tiếp tục nào cũng sẽ bắt đầu từ trạng thái j
. Chúng tôi biết rất nhiều về hệ thống này! Đây không phải là một hoạt động mạo hiểm! Khi chúng tôi đặt dvd vào ổ đĩa, nó sẽ chạy vào! Ổ đĩa dvd không nhận được bất kỳ tiếng nói nào về trạng thái sau mỗi lệnh.
Nhưng nói chung điều đó không đúng, khi tương tác với thế giới. Đôi khi bạn có thể cần phải cho đi một số quyền kiểm soát và để thế giới làm những gì nó thích. Ví dụ: nếu bạn là một máy chủ, bạn có thể cung cấp cho khách hàng của mình lựa chọn và trạng thái phiên của bạn sẽ phụ thuộc vào những gì họ chọn. Thao tác "lựa chọn ưu đãi" của máy chủ không xác định trạng thái kết quả, nhưng dù sao thì máy chủ vẫn có thể tiếp tục. Nó không phải là một "lệnh nguyên thủy" theo nghĩa trên, vì vậy các mona được lập chỉ mục không phải là một công cụ tốt để mô hình hóa kịch bản không thể đoán trước .
Công cụ nào tốt hơn?
type f :-> g = forall state. f state -> g state
class MonadIx (m :: (state -> *) -> (state -> *)) where
returnIx :: x :-> m x
flipBindIx :: (a :-> m b) -> (m a :-> m b) -- tidier than bindIx
Bánh quy đáng sợ? Không thực sự, vì hai lý do. Một, nó trông giống như một đơn nguyên hơn, bởi vì nó là một đơn nguyên, nhưng hơn (state -> *)
là hơn *
. Hai, nếu bạn nhìn vào loại mũi tên Kleisli,
a :-> m b = forall state. a state -> m b state
bạn sẽ có được kiểu tính toán với điều kiện trước a
và điều kiện sau b
, giống như trong Good Old Hoare Logic. Các xác nhận trong lôgic chương trình đã mất hơn nửa thế kỷ để vượt qua thư từ Curry-Howard và trở thành kiểu Haskell. Loại returnIx
câu nói "bạn có thể đạt được bất kỳ điều kiện hậu cần nào, chỉ cần không làm gì cả", đó là quy tắc Hoare Logic cho "bỏ qua". Thành phần tương ứng là quy tắc Hoare Logic cho ";".
Hãy kết thúc bằng cách xem xét loại bindIx
, đưa tất cả các bộ định lượng vào.
bindIx :: forall i. m a i -> (forall j. a j -> m b j) -> m b i
Các forall
s này có cực tính trái ngược nhau. Chúng tôi chọn trạng thái ban đầu i
và tính toán có thể bắt đầu tại i
, với điều kiện sau a
. Thế giới chọn bất kỳ trạng thái trung gian nào j
mà nó thích, nhưng nó phải cung cấp cho chúng ta bằng chứng rằng điều kiện hậu b
tồn tại, và từ bất kỳ trạng thái nào như vậy, chúng ta có thể tiếp tục b
giữ vững. Vì vậy, theo trình tự, chúng ta có thể đạt được điều kiện b
từ trạng thái i
. Bằng cách giải phóng sự kìm kẹp của chúng ta đối với các trạng thái "sau", chúng ta có thể lập mô hình các phép tính không thể đoán trước .
Cả hai IxMonad
và MonadIx
đều hữu ích. Cả hai mô hình tính hợp lệ của các tính toán tương tác liên quan đến trạng thái thay đổi, có thể dự đoán và không thể đoán trước, tương ứng. Khả năng dự đoán có giá trị khi bạn có thể có được nó, nhưng không thể đoán trước đôi khi là một thực tế của cuộc sống. Hy vọng rằng, câu trả lời này cung cấp một số dấu hiệu về các đơn nguyên được lập chỉ mục là gì, dự đoán cả khi chúng bắt đầu hữu ích và khi nào chúng dừng lại.
True
/False
giá trị dưới dạng đối số kiểuDVDDrive
? Đó là một số tiện ích mở rộng, hay là các boolean thực sự nhập vào đây?