6

Mã này (lấy từ Learn You A Haskell):đánh giá Lazy và IO tác dụng phụ nhầm lẫn

main = do putStr "Hey, " 
      putStr "I'm " 
      putStrLn "Andy!" 

rõ ràng desugars để

main =  putStr "Hey, " >>= 
     (\_ -> putStr "I'm " >>= 
     (\_ -> putStrLn "Andy!")) 

nào, như tôi hiểu nó có thể được interpretted nói "Để putStrLn "Andy!" Lần đầu tiên tôi cần phải đặt "Tôi", và để làm điều đó, trước tiên tôi cần phải đưa "Xin chào",

Tôi không đồng ý với cách giải thích này, điều này gây phiền toái vì t ông trình biên dịch rõ ràng là không và lá tôi cảm thấy bối rối. Vấn đề tôi có với nó là các lambdas bỏ qua lập luận của họ, trong quá trình đánh giá lười biếng không phải là loại điều này được cho là được công nhận và đoản mạch?

Ngoài ra, chắc chắn, ràng buộc trả về một hành động IO và khi hành động IO đó rơi vào chính nó sẽ được thực thi. Nhưng những gì để ngăn chặn nó từ in "Hey, Andy! Tôi"? Tôi nghi ngờ đó là bất cứ điều gì ràng buộc đang làm.

Ngoài ra, cách hoạt động IO của loại "IO()" mang đủ thông tin để cho phép hệ thống thời gian chạy in "Hey, tôi là Andy!"? Làm thế nào mà IO() khác với IO() so với bản in "Hello World!" hoặc ghi vào một tập tin?

xem xét khác, từ trang wikipedia cho đơn nguyên:

phiên bản có đường:

do 
    putStrLn "What is your name?" 
    name <- getLine 
    putStrLn ("Nice to meet you, " ++ name ++ "!") 

khử đường phiên bản:

putStrLn "What is your name?" >>= 
    (\_ -> 
     getLine >>= 
     (\name -> 
      putStrLn ("Nice to meet you, " ++ name ++ "!"))) 

tương tự câu chuyện ở đây.

Tôi nghĩ rằng tôi chỉ cần xem định nghĩa liên kết cho IO và sau đó tất cả sẽ rõ ràng. Một cái gì đó khác mà sẽ giúp rất nhiều là nếu ai đó có thể giúp tôi bước qua cách chương trình thực sự được đánh giá và xác định những khoảnh khắc chính xác khi các tác dụng phụ xảy ra.

+3

"Các lambdas bỏ qua đối số của họ, trong quá trình đánh giá lười biếng không phải là loại điều này được cho là được công nhận và đoản mạch?" Bạn đặt cược! Đối số thứ hai cho '(>> =)' là một hàm đặc biệt lười ở đây, nhưng hàm '(>> =)' không phải là lười. –

Trả lời

10

Đọc bài "Tackling the awkward squad" của Simon Peyton Jones.

Đối với câu hỏi liên quan, xem

Hãy giải thích như vậy bao gồm cả tôi với một hạt muối - không vẫy tay có thể thay thế giấy được kiểm chứng chặt chẽ, và các giải thích nhất thiết là quá đơn giản.

Một góc nhìn rất thô là >>= có thể được xem như là một nhà xây dựng danh sách:

data IO = [Primitive] 

và IO hệ thống phụ deconstructs giá trị của main và tiêu thụ danh sách đó. I E. `` main is just a list. So you may want to take a look at the definition of Haskell entry point above chính , bind` khá là không thú vị.

Bạn cũng có thể đọc các bài báo về lịch sử của haskell và xem các phiên bản trước của hệ thống con IO để biết thông tin chi tiết về những gì đang diễn ra.

Ngoài ra, hãy xem C language is purely functional bài đăng châm biếm của Conal Elliott.

Định nghĩa về độ tinh khiết của chức năng là không tầm thường và tôi nhớ một bài báo chi tiết về định nghĩa, nhưng tôi không nhớ tiêu đề.

+0

Thật thú vị khi mọi người luôn giải thích >> = trên giá trị IO bằng cách tương tự. Nó không có định nghĩa đâu đó trong khúc dạo đầu sao? Tại sao không ai có thể trích dẫn nó? Là ràng buộc trên IO, nơi ma thuật đang xảy ra? Tôi đang đọc giấy đội hình vụng về ngay bây giờ và thậm chí SPJ tránh xa khỏi việc xác định ràng buộc – TheIronKnuckle

+5

Loại 'IO' là trừu tượng, vì vậy không có định nghĩa nào về' >> = 'trong tiêu chuẩn Haskell. Tùy thuộc vào cách bạn triển khai 'IO', bạn sẽ có các triển khai khác nhau của' >> = '. Nếu bạn đi sâu vào ghc, bạn sẽ thấy rằng 'IO' là một đơn vị trạng thái và' >> = 'chỉ đơn giản là ràng buộc cho một đơn vị trạng thái. (Có nhiều ma thuật bên trong trình biên dịch để làm cho hiệu quả này.) – augustss

+0

@ThelronKnuckle Nó không phải là một sự tương tự về 'giá trị'. Nó là một sự tương tự về việc thực hiện IO thuần túy khác bằng cách sử dụng ý tưởng 'main :: [Request] -> [Response]' cũ. Ngoài ra phần thú vị nhất không phải là của các ràng buộc, nhưng của 'runIO' cho IO monad. Vì đơn nguyên IO là trừu tượng, chúng ta phải cung cấp một giải thích trừu tượng như SPJ đã làm, hoặc sử dụng một số chiến lược triển khai như tôi đã làm. – nponeccop

1

Tôi nghĩ rằng điều này là dễ hiểu hơn nếu bạn nghĩ về các hành động một lần nữa làm chức năng. Chẳng hạn ràng buộc của bạn (do { foo <- getLine ; putStrLn foo ; }) là trực giác tương tự như các chức năng sau:

apply arg func = func (arg) 

Trừ rằng chức năng là một giao dịch. Vì vậy, cuộc gọi của chúng tôi func(arg) được đánh giá, nếu chỉ khi (arg) hoàn tất thành công. Nếu không, chúng tôi fail trong hành động của chúng tôi.

Điều này khác với các chức năng bình thường vì Haskell thực sự không quan tâm nếu (arg) tính đầy đủ hoặc hoàn toàn, cho đến khi cần bit func(arg) để tiếp tục chương trình.

7

Nhìn vào IO trong quá trình triển khai Haskell thực sự có thể sẽ gây nhầm lẫn nhiều hơn là khai sáng. Nhưng nghĩ về IO như được định nghĩa như thế này (điều này giả định bạn biết GADTs):

data IO a where 
    Return a :: IO a 
    Bind :: IO a -> (a -> IO b) -> IO b 
    PutStr :: String -> IO() 
    GetLine :: IO String 

instance Monad IO where 
    return = Return 
    (>>=) = Bind 

putStr :: String -> IO() 
putStr = PutStr 

getLine :: IO String 
getLine = GetLine 

Vì vậy, khi bạn đánh giá một chương trình (loại IO()) tất cả nó là xây dựng một cấu trúc dữ liệu kiểu IO() mô tả cách sự tương tác với thế giới sẽ xảy ra khi bạn thực hiện nó. Sau đó, bạn có thể tưởng tượng động cơ thực thi đang được viết, ví dụ: C và có tất cả các hiệu ứng xảy ra.

Vì vậy

main = do putStr "Hey, " 
      putStr "I'm " 
      putStrLn "Andy!" 

cũng giống như

main = Bind (PutStr "Hey, ") (\ _ -> Bind (PutStr "I'm ") (\ _ -> PutStr "Andy!")) 

Và trình tự của những xuất phát từ đường cơ thực hiện hoạt động.

Điều đó nói rằng, tôi biết không có triển khai Haskell thực sự thực hiện theo cách này. Việc triển khai thực sự có xu hướng triển khai IO làm đơn vị trạng thái với mã thông báo đại diện cho thế giới thực được truyền xung quanh (đây là điều đảm bảo trình tự) và các nguyên thủy như putStr chỉ là các cuộc gọi đến các hàm C.

+0

+1 cho phương pháp tiếp cận GADT và 'Nhìn vào IO trong một thực hiện Haskell thực sự có thể sẽ gây nhầm lẫn nhiều hơn điều enlightens'. – nponeccop

3

Tôi nghĩ rằng tôi chỉ cần xem định nghĩa liên kết cho IO và sau đó nó sẽ rõ ràng.

Có, bạn nên làm điều đó.Nó thực sự khá dễ dàng, và nếu tôi nhớ chính xác nó đi như thế

newtype IO = IO (RealWorld -> (a, RealWorld)) 

(IO f) >>= g = ioBind f g 
    where 
     ioBind :: (RealWorld -> (a, RealWorld)) -> (a -> IO b) -> RealWorld -> (b, RealWorld) 
     ioBind f g rw = case f rw of 
      (a, [email protected]) -> case g a of 
       IO b -> b rw 

Các "lừa" là tất cả các giá trị IO là thực sự cơ bản là một chức năng, nhưng để đánh giá nó, bạn sẽ cần một mã thông báo kiểu RealWorld. Chỉ có một cá thể có thể cung cấp một giá trị như vậy - hệ thống thời gian chạy chạy chính (và, tất nhiên, hàm không được đặt tên).