2011-02-06 36 views
11

[Câu hỏi này được thúc đẩy bởi Chương 9 trong "Real World Haskell"]

Dưới đây là một chức năng đơn giản (cắt xuống đến yếu tố cần thiết):

saferOpenFile path = handle (\_ -> return Nothing) $ do 
     h <- openFile path ReadMode 
     return (Just h) 

Tại sao tôi cần điều đó $?

Nếu đối số thứ hai để xử lý không phải là một khối, tôi không cần nó. Các công trình sau đây chỉ hoạt động tốt:

handle (\_ -> putStrLn "Error calculating result") (print x) 

Khi tôi cố gắng xóa việc biên soạn $ không thành công. Tôi có thể lấy nó để làm việc nếu tôi một cách rõ ràng thêm dấu ngoặc, ví dụ:

saferOpenFile path = handle (\_ -> return Nothing) (do 
     h <- openFile path ReadMode 
     return (Just h)) 

tôi có thể hiểu rằng, nhưng tôi đoán tôi hy vọng rằng khi Haskell chạm do nó nên suy nghĩ "Tôi bắt đầu một khối", và chúng ta không cần phải đặt rõ ràng $ ở đó để phá vỡ mọi thứ.

Tôi cũng nghĩ về đẩy khối việc phải làm để dòng tiếp theo, như thế này:

saferOpenFile path = handle (\_ -> return Nothing) 
    do 
    h <- openFile path ReadMode 
    return (Just h) 

nhưng điều đó không làm việc mà không Parens một trong hai. Gây bối rối cho tôi, bởi vì các công việc sau:

add a b = a + b 

addSeven b = add 7 
    b 

tôi chắc chắn rằng tôi sẽ chỉ đạt được điểm mà tôi chấp nhận nó như "đó chỉ là cách bạn viết thành ngữ Haskell", nhưng không ai có bất kỳ góc độ để cung cấp cho ? Cảm ơn trước.

+0

Xin lỗi, mọi người, tôi không biết stackoverflow rất tốt. Mã chỉ là thùng rác. (Tại sao không có "Câu hỏi Xem trước"). Tôi sẽ cố gắng đăng lại nó. – Jonathan

+0

chỉ cần nhấn nút "mã" trên thanh công cụ của trình soạn thảo (trông giống như một cặp dấu ngoặc ôm). Ngoài ra, bản xem trước ở ngay bên dưới nơi bạn nhập, cập nhật theo thời gian thực ... – Jon

+1

Ah, NoScript cần thiết để cho phép thêm một miền nữa! Có vẻ ổn thôi. Cảm ơn, Jon! – Jonathan

Trả lời

6

Theo Haskell 2010 report, do desugars như thế này:

do {e;stmts} = e >>= do {stmts}

do {p <- e; stmts} = let ok p = do {stmts} 
         ok _ = fail "..." 
        in e >>= ok 

Đối với trường hợp thứ hai, nó giống để viết nó desugaring như thế này (và dễ dàng hơn cho các mục đích minh chứng):

do {p <- e; stmts} = e >>= (\p -> do stmts)

Vì vậy, giả sử bạn viết này:

-- to make these examples shorter 
saferOpenFile = f 
o = openFile 

f p = handle (\_ -> return Nothing) do 
    h <- o p ReadMode 
    return (Just h) 

Nó desugars này:

f p = handle (\_ -> return Nothing) (o p ReadMode) >>= (\h -> return (Just h)) 

Mà là giống như thế này:

f p = (handle (\_ -> return Nothing) (o p ReadMode)) >>= (\h -> return (Just h)) 

Mặc dù bạn có thể dành cho nó để có nghĩa là này:

handle (\_ -> return Nothing) ((o p ReadMode) >>= (\h -> return (Just h))) 

tl; dr - khi nào cú pháp được desugared, nó không ngoặc đơn toàn bộ tuyên bố như bạn sẽ nghĩ rằng nó nên (như của Haskell 2010).

Câu trả lời này chỉ AFAIK và

  1. có thể không đúng
  2. có thể một ngày nào đó lỗi thời nếu nó là đúng

Lưu ý: nếu bạn nói với tôi ", nhưng việc sử dụng desugaring thực tế một tuyên bố 'let' rằng nên nhóm e >>= ok, phải không? ", sau đó tôi sẽ trả lời giống như tl; dr cho báo cáo: nó không ngoặc/nhóm như bạn nghĩ rằng nó.

+0

Vâng, tôi đoán báo cáo làm cho câu trả lời này dứt khoát. Tôi đoán tôi chỉ có một thời gian khó khăn hơn làm quen với ứng dụng chức năng phong cách lambda-tính toán của Haskell mà tôi nghĩ rằng tôi đã. – Jonathan

+0

Nó thực sự là một sự kết hợp của câu trả lời này và Callum của đó rút ra câu trả lời hoàn chỉnh cho câu hỏi của bạn. Điều này giải thích tại sao '$' là cần thiết và cái kia để giải thích nó làm gì. –

+2

Không phải câu trả lời này. De-sugaring luôn luôn hoạt động trên các cây cú pháp trừu tượng đã hoàn thành, có nghĩa là nó có đầu vào 'đầy đủ dấu ngoặc đơn' và tạo ra đầu ra 'đầy đủ dấu ngoặc đơn'. Nó không thay đổi định nghĩa của đầu vào hợp lệ, hoặc khi bạn cần dấu ngoặc đơn. (Nó được định nghĩa giống như các macro Lisp hơn là các macro đọc Lisp hoặc các macro chuỗi mã thông báo của C). –

10

Điều này là do thứ tự hoạt động của Haskell. Ứng dụng chức năng liên kết chặt chẽ, có thể là một nguồn gây nhầm lẫn. Ví dụ:

add a b = a + b 
x = add 1 add 2 3 

Haskell diễn giải điều này là: thêm chức năng áp dụng cho 1 và chức năng thêm. Đây có lẽ không phải là ý tưởng của lập trình viên. Haskell sẽ phàn nàn về việc mong đợi một chữ số Num cho đối số thứ hai, nhưng thay vào đó sẽ nhận được một hàm.

Có hai giải pháp:

1) Khái niệm có thể được ngoặc:

x = add 1 (add 2 3) 

nào Haskell sẽ giải thích như sau: chức năng add áp dụng cho 1, thì giá trị của thêm 2 3.

Nhưng nếu làm tổ quá sâu, điều này có thể gây nhầm lẫn, do đó giải pháp thứ hai.

2) Các nhà điều hành $:

x = add 1 $ add 2 3 

$ là một nhà điều hành áp dụng một chức năng để đối số của nó. Haskell đọc như sau: function (add 1) áp dụng cho giá trị của add 2 3. Hãy nhớ rằng trong Haskell, các hàm có thể được áp dụng một phần, vì vậy (add 1) là một hàm hoàn toàn hợp lệ của một đối số.

Các $ nhà điều hành có thể được sử dụng nhiều lần:

x = add 1 $ add 2 $ add 3 4 

Những giải pháp bạn chọn sẽ được xác định bằng cách mà bạn nghĩ là dễ đọc hơn trong một bối cảnh cụ thể.

+3

Cảm ơn, Callum. Tôi đoán rằng ($) chủ yếu là về cú pháp khi tôi đã cố gắng để viết một định nghĩa của nó và đến với ($) f x = f x. Từ các nguồn hạn chế của tôi, có vẻ như Haskellers thực sự thích tránh parens, nếu có thể. Tôi đoán nó khó đến từ các ngôn ngữ khác, nơi tôi giỏi đọc thêm (1, thêm (3, 4)). Đặc biệt là khi có các combinator đang được sử dụng – Jonathan

10

Điều này thực sự không hoàn toàn cụ thể đối với do -notation. Bạn cũng không thể viết những thứ như print if x then "Yes" else "No" hoặc print let x = 1+1 in x*x.

Bạn có thể xác minh điều này từ định nghĩa ngữ pháp vào đầu chapter 3 of the Haskell Report: một biểu thức do hoặc một điều kiện hoặc một biểu thức let là một lexp, nhưng lập luận của ứng dụng chức năng là một aexp, và một lexp thường không hợp lệ là aexp.

Điều này không cho bạn biết tại sao lựa chọn đó được thực hiện trong Báo cáo, tất nhiên. Nếu tôi phải đoán, tôi có thể phỏng đoán rằng nó sẽ mở rộng quy tắc hữu ích "ứng dụng chức năng gắn chặt hơn bất kỳ thứ gì khác" với sự ràng buộc giữa, ví dụ: do và khối của nó.Nhưng tôi không biết đó có phải là động lực ban đầu hay không. (Tôi thấy các biểu mẫu bị từ chối như print if x then ... khó đọc, nhưng điều đó có thể chỉ vì tôi đã quen đọc mã mà Haskell chấp nhận.)

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