2014-04-03 16 views
6

tôi nghĩ rằng tôi đã có một xử lý tốt về Haskell Monads cho đến khi tôi nhận ra mảnh này rất đơn giản mã đã không có ý nghĩa với tôi (đây là từ haskell wiki about the State monad):Tại sao tôi có thể gọi một hàm monadic mà không cung cấp một đơn nguyên?

playGame :: String -> State GameState GameValue 
playGame []  = do 
    (_, score) <- get 
    return score 

gì confuses me là, tại sao là mã được phép gọi "get", khi đối số duy nhất được cung cấp là một chuỗi? Có vẻ như nó đang kéo giá trị ra khỏi không khí mỏng.

Cách tốt hơn cho tôi để đặt câu hỏi có thể là, cách viết lại hàm này bằng cách sử dụng >>= và lambda thay vì ký hiệu? Tôi không thể tự mình tìm ra được.

+5

Mã không phải là "đang gọi". "get" không phải là một hàm, đó là một giá trị đa hình: http://hackage.haskell.org/packages/archive/transformers/latest/doc/html/Control-Monad-Trans-State-Lazy.html#v: nhận được –

+1

Cảm ơn, đó thực sự là một điểm chính của sự nhầm lẫn đối với tôi. Nó gần như là một cái tên không may cho một ai đó đến từ thế giới OO, nơi các lớp thường có các phương thức "getThis" và "getThat". Tôi chỉ giả định rằng vì "get" là một động từ, nó phải là một hàm. –

+0

Nó không phải là đơn giản, ngay cả trong đất OO. Đặc biệt là vì các bản ghi OO không hoạt động - chúng thực sự gần gũi hơn với các giá trị đa hình, giống như của Haskell. Sự khác biệt là đơn nguyên trong các ngôn ngữ OO gắn giá trị cho một đối tượng, trong khi chúng ta đính kèm giá trị cho các monads tùy ý với ngữ nghĩa tùy ý. Tôi sẽ trả lời giải thích ý tôi là gì. – nomen

Trả lời

8

Desugaring này vào do ký hiệu sẽ trông như thế

playGame [] = 
    get >>= \ (_, score) -> 
    return score 

Chúng tôi cũng có thể chỉ cần viết này với fmap

playGame [] = fmap (\(_, score) -> score) get 
playGame [] = fmap snd get 

Bây giờ Bí quyết là để nhận ra rằng get là một giá trị giống như bất kỳ khác với loại

State s s 

Những gì get sẽ trả về sẽ không được xác định cho đến khi chúng tôi cho chúng tôi tính toán runState hoặc tương tự như vậy khi chúng tôi cung cấp giá trị bắt đầu rõ ràng cho tiểu bang của chúng tôi.

Nếu chúng ta đơn giản hóa này hơn nữa và thoát khỏi các đơn nguyên nhà nước chúng tôi có

playGame :: String -> (GameState -> (GameState, GameValue)) 
playGame [] = \gamestate -> (gamestate, snd gamestate) 

Các đơn nguyên nhà nước chỉ được quấn xung quanh tất cả những đường chuyền hướng dẫn này của GameState nhưng bạn có thể nghĩ get như truy cập vào giá trị mà "hàm" của chúng ta đã được thông qua.

+0

Vì vậy, theo một nghĩa nào đó, nhà nước monad đang ẩn đi qua của nhà nước (như bạn đã đề cập). Tôi có thể đánh giá cao "ma thuật" ở đây. Nhưng chắc chắn trong ruột của runState là nơi mà giá trị của "get" được "giữ" ... phải không? –

+1

@ parker.sikand Vì vậy, 'State' chỉ là một wrapper trên đầu trang của một hàm chữ' s -> (s, a) '. 'runState' là những gì chúng ta sử dụng để thực sự * áp dụng * hàm này. Bạn có thể nghĩ về nó như một loại "được canh" lên '$'. Vì vậy, tôi cho rằng 'runState' giữ giá trị của' get' nhiều như trong '(\ x -> ...) $ a'' $ 'giữ giá trị của' x' – jozefg

0

Một đơn nguyên là một "điều" có ngữ cảnh (chúng tôi gọi nó là m) và "tạo ra" một giá trị nào, trong khi vẫn tôn trọng luật đơn nguyên. Chúng ta có thể nghĩ về điều này dưới dạng "bên trong" và "bên ngoài" của đơn nguyên. Luật đơn nguyên cho chúng ta biết cách đối phó với một "chuyến đi khứ hồi" - đi ra ngoài và sau đó quay trở lại bên trong. Đặc biệt, các luật cho chúng ta biết rằng m (m a) về cơ bản là cùng loại như (m a).

Vấn đề là một đơn nguyên là sự khái quát hóa điều này. tham gia squashes (m (m a)) vào (m a) 's, và (>> =) kéo một giá trị ra khỏi đơn nguyên và áp dụng một hàm vào đơn nguyên. Nói cách khác, nó áp dụng một hàm (f :: a -> m b) vào một trong (m a) - tạo ra một (m (m b)), và sau đó squashes rằng thông qua tham gia để có được (m b) của chúng tôi.

Vì vậy, điều này phải làm gì với 'get' và các đối tượng?

Vâng, ký hiệu đặt chúng ta lên để kết quả của phép tính là đơn nguyên của chúng ta. Và (< -) cho phép chúng tôi kéo một giá trị ra khỏi một đơn nguyên, để chúng tôi có thể liên kết nó với một chức năng, trong khi vẫn được gọi là bên trong của đơn nguyên. Vì vậy, ví dụ:

doStuff = do 
    a <- get 
    b <- get 
    return $ (a + b) 

Lưu ý rằng a và b là thuần khiết. Họ là "bên ngoài" của nhận được, bởi vì chúng tôi thực sự nhìn trộm bên trong nó. Nhưng bây giờ chúng ta có một giá trị bên ngoài của đơn nguyên, chúng ta cần phải làm một cái gì đó với nó (+) và sau đó dính nó trở lại trong đơn nguyên.

Đây chỉ là một chút ký hiệu gợi ý, nhưng nó có thể được tốt đẹp nếu chúng ta có thể làm:

doStuff = do 
    a  <- get 
    b  <- get 
    (a + b) -> (\x -> return x) 

để thực sự nhấn mạnh phía sau và ra của nó. Khi bạn hoàn thành một hành động đơn lẻ, bạn phải nằm ở cột bên phải trong bảng đó, bởi vì khi hành động được thực hiện, 'tham gia' sẽ được gọi để làm phẳng các lớp. (Ít nhất, khái niệm)

Ồ, đúng, đồ vật. Vâng, rõ ràng, một ngôn ngữ OO về cơ bản sống và thở trong một đơn nguyên IO của một số loại. Nhưng chúng tôi thực sự có thể phá vỡ nó một số chi tiết. Khi bạn chạy một cái gì đó dọc theo dòng:

x = foo.bar.baz.bin() 

cơ bản bạn đang chạy một chồng đơn nguyên biến, trong đó có một bối cảnh IO, trong đó sản xuất một bối cảnh foo, trong đó sản xuất một bối cảnh thanh, trong đó sản xuất một bối cảnh baz, mà tạo ra một bối cảnh thùng. Và sau đó hệ thống thời gian chạy "cuộc gọi" tham gia vào điều này nhiều lần khi cần thiết. Lưu ý ý tưởng này phù hợp với "ngăn xếp cuộc gọi" như thế nào. Và quả thực, đây là lý do tại sao nó được gọi là một "biến áp monad stack" ở phía haskell. Đó là một đống bối cảnh đơn thuần.

Các vấn đề liên quan