2016-05-27 13 views
14

Given:Hiểu Loại IO() trong `let` Biểu

λ: let f = putStrLn "foo" in 42 
42 

f 's loại là gì? Tại sao "foo" không được in trước khi hiển thị kết quả của 42?

Cuối cùng, tại sao không có tác phẩm sau?

λ: :t f 

<interactive>:1:1: Not in scope: ‘f’ 
+0

Bạn có thể muốn đọc về ['let' vs' <-'] (http://stackoverflow.com/q/28624408/3234959). Nó không phải là vấn đề chính xác bạn đang có bây giờ, nhưng nó có thể giúp đỡ. – chi

+0

Đọc [Tại sao tôi không thể ép buộc hành động IO với seq?] (Http://stackoverflow.com/q/20324446/510937) để biết thêm thông tin về đánh giá và thực thi các hành động IO. – Bakuriu

Trả lời

12

loại f 's là gì?

Như bạn đã xác định được một cách chính xác, nó là IO() có thể được coi là một hành động IO trả về không có gì hữu ích (())

Tại sao "foo" không được in trước khi hiển thị kết quả của 42?

Haskell được đánh giá nhẹ nhàng, nhưng thậm chí seq là không đủ trong trường hợp này. Một hành động IO sẽ chỉ được thực hiện trong REPL nếu biểu thức trả về hành động IO. Một hành động IO sẽ chỉ được thực hiện trong một chương trình nếu nó được trả về bởi main. Tuy nhiên, có nhiều cách để vượt qua giới hạn này.

Cuối cùng, tại sao công việc sau không?

Haskell's let đặt tên giá trị trong phạm vi biểu thức, vì vậy sau khi biểu thức đã được đánh giá f nằm ngoài phạm vi.

7

f sẽ thuộc loại IO().

"foo" không được in vì f không được 'gắn kết' với thế giới thực. (Tôi không thể nói đây là một lời giải thích thân thiện. Nếu điều này nghe có vẻ vô nghĩa, bạn có thể muốn tham khảo một số hướng dẫn để nắm bắt ý tưởng về đánh giá đơn giản và lười biếng).

let name = value in (scope) làm cho giá trị có sẵn trong, nhưng không nằm ngoài phạm vi, vì vậy :t sẽ không tìm thấy nó trong phạm vi cấp cao nhất của ghci.

let mà không in làm cho nó có sẵn cho :t (mã này chỉ có giá trị trong ghci):

> let f = putStrLn "foo" 
> :t f 
f :: IO() 
9

let f = ... chỉ đơn giản định nghĩa f, và không "chạy" bất cứ điều gì. Nó là mơ hồ tương tự như một định nghĩa của một chức năng mới trong lập trình bắt buộc.

mã đầy đủ của bạn let f = putStrLn "foo" in 42 có thể được lược dịch

{ 
    function f() { 
    print("foo"); 
    } 
    return 42; 
} 

Bạn sẽ không mong đợi ở trên để in bất cứ điều gì, phải không?

Để so sánh, let f = putStrLn "foo" in do f; f; return 42 cũng tương tự như

{ 
    function f() { 
    print("foo"); 
    } 
    f(); 
    f(); 
    return 42; 
} 

Sự tương ứng là không hoàn hảo, nhưng hy vọng bạn sẽ có được ý tưởng.

4

Có hai điều đang diễn ra tại đây.

Đầu tiên, hãy xem xét

let x = sum [1..1000000] in 42 

Haskell là lười biếng. Vì chúng tôi không thực sự làm bất cứ điều gì với x, nó không bao giờ được tính toán. Thật vậy, vì nó sẽ hơi chậm.) Thật vậy, nếu bạn biên dịch điều này, trình biên dịch sẽ thấy rằng x không bao giờ được sử dụng và xóa nó (tức là, không tạo ra bất kỳ mã được biên dịch nào cho nó).

Thứ hai, gọi putStrLn không thực sự in bất kỳ thứ gì. Thay vào đó, nó trả về IO(), mà bạn có thể nghĩ là một loại "đối tượng lệnh I/O". Chỉ có một đối tượng lệnh khác với việc thực hiện đối tượng đó. Theo thiết kế, cách duy nhất để "thực hiện" một đối tượng lệnh I/O là trả về nó từ main. Ít nhất, nó nằm trong một chương trình hoàn chỉnh; GHCi có tính năng hữu ích nếu bạn nhập một biểu thức trả về một đối tượng lệnh I/O, GHCi sẽ thực thi nó cho bạn.

Biểu thức của bạn trả về 42; một lần nữa, f không được sử dụng, do đó, nó không làm bất cứ điều gì.

chi đúng chỉ ra, nó giống như tuyên bố một hàm cục bộ (không đối số) nhưng không bao giờ gọi nó. Bạn sẽ không mong đợi để xem bất kỳ đầu ra.

Bạn cũng có thể làm điều gì đó như

actions = [print 5, print 6, print 7, print 8] 

Điều này tạo ra một danh sách các đối tượng tôi/O lệnh. Nhưng, một lần nữa, nó không thực hiện bất kỳ trong số chúng.

Thông thường, khi bạn viết một hàm có I/O, đó là một khối lệnh chặn mọi thứ vào một đối tượng lệnh I/O khổng lồ và trả về cho người gọi. Trong trường hợp đó, bạn thực sự không cần phải hiểu hay không về sự khác biệt này giữa xác định đối tượng lệnh và thực hiện. Nhưng sự khác biệt vẫn còn đó.

Có thể dễ dàng thấy điều này hơn với một đơn vị có chức năng chạy rõ ràng. Ví dụ: runST lấy đối tượng lệnh ST, chạy đối tượng đó và cung cấp cho bạn câu trả lời. Nhưng (nói) newSTVar, tự nó không làm gì ngoài việc xây dựng một lệnh ST; bạn phải runST trước khi bất cứ điều gì thực sự "xảy ra".

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