2013-02-27 21 views
5

Tôi đang gặp rắc rối hiểu cách biểu Haskell này hoạt động:Xin giải thích (forM_ [stdout, stderr] lật hPutStrLn.) :: String -> IO()

import Control.Monad 
import System.IO 
(forM_ [stdout, stderr] . flip hPutStrLn) "hello world" 

các . flip hPutStrLn phần làm chính xác là gì ? Chữ ký loại có vẻ phức tạp:

ghci> :type flip 
flip :: (a -> b -> c) -> b -> a -> c 
ghci> :type (.) 
(.) :: (b -> c) -> (a -> b) -> a -> c 
ghci> :type (. flip) 
(. flip) :: ((b -> a -> c1) -> c) -> (a -> b -> c1) -> c 
ghci> :type (. flip hPutStrLn) 
(. flip hPutStrLn) :: ((Handle -> IO()) -> c) -> String -> c 

Toán hạng trái và phải của toán tử (.) khi biểu thức được đánh giá là gì?

Một cách khác để đặt câu hỏi của tôi là, làm thế nào một phần trái của biểu thức vào cuối đầu với một loại chữ ký như thế này:

(forM_ [stdout, stderr] . flip hPutStrLn) :: String -> IO() 
+0

so sánh ': type (. HPutStrLn)' với ': type (. Flip hPutStrLn)' help at all? –

+1

Ai sẽ viết mã như vậy? Phong cách điểm miễn phí thực sự vô nghĩa ở đây và làm tổn thương khả năng đọc, IMHO –

+1

@NiklasB. nó không quá tệ với tôi, mặc dù tôi nhớ một thời gian trong quá khứ khi nó sẽ hoàn toàn mờ đục. Nó phụ thuộc vào sự quen thuộc của bạn với phong cách, tôi nghĩ (hay đúng hơn là, rõ ràng). – luqui

Trả lời

13

Các toán hạng bên trái và bên phải của (.)

forM_ [stdout, stderr] 

flip hPutStrLn 

tương ứng.

Loại hPutStrLn

hPutStrLn :: Handle -> String -> IO() 

nên flip hPutStrLn có kiểu

flip hPutStrLn :: String -> Handle -> IO() 

Khi hệ thống kiểu cho bạn biết, flip là một combinator rằng giao dịch hoán đổi thứ tự của các đối số của một hàm khác. Quy định tại các trừu tượng

flip  :: (a -> b -> c) -> b -> a -> c 
flip f x y = f y x 

Từ ghci bạn đã biết rằng các loại (. flip hPutStrLn)

ghci> :type (. flip hPutStrLn) 
(. flip hPutStrLn) :: ((Handle -> IO()) -> c) -> String -> c 

Làm việc từ một hướng khác, loại phía bên trái là

ghci> :type forM_ [stdout, stderr] 
forM_ [stdout, stderr] :: Monad m => (Handle -> m b) -> m() 

Quan sát làm thế nào các loại phù hợp với nhau.

(. flip hPutStrLn)  ::   ((Handle -> IO()) -> c ) -> String -> c 
forM_ [stdout, stderr] :: Monad m => (Handle -> m b) -> m() 

Kết hợp hai (gọi là người đầu tiên với thứ hai) cho

ghci> :type forM_ [stdout, stderr] . flip hPutStrLn 
forM_ [stdout, stderr] . flip hPutStrLn :: String -> IO() 

Trong câu hỏi của bạn, kết quả của các thành phần được áp dụng cho một String, và điều đó tạo ra một hành động I/O rằng sản lượng (), tức là, chúng tôi chủ yếu quan tâm đến các tác dụng phụ của việc ghi vào đầu ra chuẩn và luồng lỗi.

Với point-free style chẳng hạn như định nghĩa trong câu hỏi của bạn, lập trình viên xác định các hàm phức tạp hơn về các hàm nhỏ hơn, đơn giản hơn bằng cách soạn chúng với (.). Bộ kết hợp flip rất hữu ích cho việc sắp xếp lại các đối số để làm cho các ứng dụng một phần lặp lại phù hợp với nhau.

+0

Câu trả lời rất sáng suốt. Cảm ơn bạn. – dan

+1

@dan Bạn được chào đón! Gắn bó với nó. Hành trình học Haskell thật tuyệt vời. –

7

flip đảo ngược các đối số của một hàm số đầu vào, ví dụ:

flip hPutStrLn == \a b -> hPutStrLn b a 

Các . là một nhà điều hành hàm hợp (hoặc chức năng ghi vào), cho phép bạn độc đáo chức năng chuỗi với nhau. Nếu không có toán tử này biểu hiện của bạn có thể được viết lại như sau:

forM_ [stdout, stderr] ((flip hPutStrLn) "hello world") 

mà là giống như:

forM_ [stdout, stderr] (flip hPutStrLn "hello world") 

hay, sử dụng toán tử ứng dụng:

forM_ [stdout, stderr] $ flip hPutStrLn "hello world" 

Liên quan đến . câu hỏi toán hạng. Xem xét các loại chữ ký của .:

(.) :: (b -> c) -> (a -> b) -> a -> c 

Bạn có thể xem nó như là một chức năng từ 3 đối số: một chức năng b -> c, một chức năng a -> b và một giá trị a - một giá trị kết quả c, mà còn do Currying, bạn có thể xem nó dưới dạng hàm từ hai đối số: b -> ca -> b - đến hàm kết quả thuộc loại a -> c. Và đây là những gì xảy ra trong ví dụ của bạn: bạn vượt qua hai chức năng (forM_ [stdout, stderr]flip hPutStrLn, chính chúng là kết quả của currying) đến . và nhận được hàm số loại String -> IO().

+3

Cái nào giống với 'forM_ [stdout, stderr] (\ h -> hPutStrLn h" hello world ")' – luqui

+0

Toán hạng trái và phải của hàm (.) Trong ví dụ trên là gì? – dan

+0

@dan Xem bản cập nhật –

2

Dưới đây là một dẫn xuất hơi ngắn hơn về loại đó (như được đề cập trong phần 2 của câu trả lời của Nikita Volkov).

Biết (.) :: (b -> c) -> (a -> b) -> a -> c(f . g) x = f (g x), do đó

(f . g) :: a -> c  where g :: (a -> b) and f :: (b -> c) 

(các b trong a -> bb -> c biến mất sau khi thực hiện the unification, đưa ra các loại a -> c) và kể từ

flip hPutStrLn   :: String -> (Handle -> IO())    -- g 
forM_ [stdout, stderr] :: (Monad m) => (Handle -> m b) -> m()  -- f 

(chúng tôi đặt dấu ngoặc xung quanh các Handle -> IO() trong loại đầu tiên, sử dụng thực tế rằng trong các loại -> là kết hợp đúng), loại kết quả của sáng tác thứ hai với người đầu tiên (thông qua các nhà điều hành function composition) là

(Monad m) => String -> m()  where m ~ IO and b ~() 
           (found by unification of 
             Handle -> IO() and 
             Handle -> m b  ) 

ví dụ String -> IO().

Thứ tự của các đối số cho (.) mất nhiều thời gian để làm quen; nó sẽ kích hoạt hàm đối số thứ hai của nó đầu tiên, và sau đó sử dụng kết quả để gọi hàm đối số đầu tiên của nó.Nếu chúng tôi nhập Control.Arrow thì chúng tôi có thể sử dụng toán tử >>> tương tự như (.) ngược lại, với các chức năng: (f . g) x == (g >>> f) x.

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