2011-08-29 25 views
11

Tôi đang chơi xung quanh với thất bại composable và quản lý để viết một hàm với chữ kýLàm thế nào để viết mà không cần ký hiệu Đỗ

getPerson :: IO (Maybe Person) 

nơi một người là:

data Person = Person String Int deriving Show 

Nó hoạt động và tôi đã viết nó vào ký hiệu như sau:

import Control.Applicative 

getPerson = do 
    name <- getLine -- step 1 
    age <- getInt -- step 2 
    return $ Just Person <*> Just name <*> age 

nơi

getInt :: IO (Maybe Int) 
getInt = do 
    n <- fmap reads getLine :: IO [(Int,String)] 
    case n of 
     ((x,""):[]) -> return (Just x) 
     _ -> return Nothing 

Tôi đã viết hàm này với mục đích tạo ra các lỗi có thể tổng hợp. Mặc dù tôi có ít kinh nghiệm với các đơn vị khác ngoài Có lẽ và IO, điều này có vẻ như nếu tôi có một kiểu dữ liệu phức tạp hơn với nhiều trường hơn, việc tính toán chuỗi sẽ không phức tạp.

Câu hỏi của tôi là làm cách nào để tôi viết lại điều này mà không cần ký hiệu? Vì tôi không thể ràng buộc các giá trị với tên như tên tuổi hay tôi không thực sự chắc chắn bắt đầu từ đâu.

Lý do yêu cầu chỉ đơn giản là cải thiện sự hiểu biết của tôi về (>> =) và (< *>) và soạn các lỗi và thành công (không phải viết mã của tôi với một lớp lót không đọc được).

Chỉnh sửa: Tôi nghĩ tôi nên làm rõ, "làm cách nào tôi nên viết lại getPerson mà không cần ký hiệu", tôi không quan tâm đến hàm getInt một nửa.

+2

Xem thêm: [Báo cáo Haskell 2010> Biểu thứC# Làm biểu thức] (http://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-470003.14) –

+1

Chỉ cần để nitpick, lưu ý rằng 'getPerson' isn 't một hàm, vì nó không có '->' trong kiểu chữ ký của nó; nếu bạn muốn một tên chính xác hơn "giá trị", tôi sẽ đi với "hành động IO". Xem ["Mọi thứ đều là một hàm" trong Haskell?] (Http://conal.net/blog/posts/everything-is-a-function-in-haskell) để biết thêm về điều này. –

Trả lời

20

Đỗ-ký hiệu desugars đến (>> =) cú pháp theo cách này:

getPerson = do 
    name <- getLine -- step 1 
    age <- getInt -- step 2 
    return $ Just Person <*> Just name <*> age 

getPerson2 = 
    getLine >>= 
    (\name -> getInt >>= 
    (\age -> return $ Just Person <*> Just name <*> age)) 

mỗi dòng trong do-ký hiệu, sau khi lần đầu tiên, được dịch sang một lambda sau đó được ràng buộc với dòng trước đó . Đó là một quá trình hoàn toàn cơ học để liên kết các giá trị với tên. Tôi không thấy cách sử dụng ký hiệu hay không sẽ ảnh hưởng đến khả năng tổng hợp; nó hoàn toàn là vấn đề cú pháp.

chức năng khác của bạn cũng tương tự như:

getInt :: IO (Maybe Int) 
getInt = do 
    n <- fmap reads getLine :: IO [(Int,String)] 
    case n of 
     ((x,""):[]) -> return (Just x) 
     _ -> return Nothing 

getInt2 :: IO (Maybe Int) 
getInt2 = 
    (fmap reads getLine :: IO [(Int,String)]) >>= 
    \n -> case n of 
     ((x,""):[]) -> return (Just x) 
     _    -> return Nothing 

Một vài gợi ý cho việc định hướng bạn dường như đứng đầu:

Khi sử dụng Control.Applicative, nó thường hữu ích để sử dụng <$> để nâng chức năng tinh khiết vào đơn nguyên . Có một cơ hội tốt cho điều này trong dòng cuối cùng:

Just Person <*> Just name <*> age 

trở thành

Person <$> Just name <*> age 

Ngoài ra, bạn nên nhìn vào máy biến áp đơn nguyên. Gói mtl phổ biến nhất vì nó đi kèm với Nền tảng Haskell, nhưng có các tùy chọn khác. Biến thế Monad cho phép bạn tạo ra một đơn nguyên mới với hành vi kết hợp của các monads cơ bản. Trong trường hợp này, bạn đang sử dụng các hàm có loại IO (Maybe a). Các mtl (thực sự là một thư viện cơ sở, máy biến áp) định nghĩa

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } 

Đây là giống như kiểu bạn đang sử dụng, với m biến instantiated tại IO.Điều này có nghĩa bạn có thể viết:

getPerson3 :: MaybeT IO Person 
getPerson3 = Person <$> lift getLine <*> getInt3 

getInt3 :: MaybeT IO Int 
getInt3 = MaybeT $ do 
    n <- fmap reads getLine :: IO [(Int,String)] 
    case n of 
     ((x,""):[]) -> return (Just x) 
     _    -> return Nothing 

getInt3 là giống hệt nhau ngoại trừ cho các nhà xây dựng MaybeT. Về cơ bản, bất cứ khi nào bạn có m (Maybe a), bạn có thể bọc nó trong MaybeT để tạo một MaybeT m a. Điều này có khả năng tương thích đơn giản hơn, như bạn có thể thấy theo định nghĩa mới của getPerson3. Chức năng đó không lo lắng về sự thất bại vì tất cả đều được xử lý bởi hệ thống ống nước của MightT. Một phần còn lại là getLine, chỉ là IO String. Điều này được đưa vào đơn vị MightT bằng hàm lift.

Chỉnh sửa nhận xét của newacct gợi ý rằng tôi cũng nên cung cấp ví dụ mẫu phù hợp; về cơ bản nó giống với một ngoại lệ quan trọng. Hãy xem xét ví dụ này (danh sách đơn nguyên là đơn nguyên chúng tôi quan tâm, Maybe là chỉ có cho phù hợp với mô hình):

f :: Num b => [Maybe b] -> [b] 
f x = do 
    Just n <- x 
    [n+1] 

-- first attempt at desugaring f 
g :: Num b => [Maybe b] -> [b] 
g x = x >>= \(Just n) -> [n+1] 

Đây g thực hiện chính xác những điều tương tự như f, nhưng những gì nếu mô hình phù hợp không?

Prelude> f [Nothing] 
[] 

Prelude> g [Nothing] 
*** Exception: <interactive>:1:17-34: Non-exhaustive patterns in lambda 

Điều gì đang xảy ra? Trường hợp cụ thể này là lý do cho một trong những mụn cóc lớn nhất (IMO) trong Haskell, phương pháp fail của lớp học Monad. Trong ký hiệu, khi một mẫu khớp không thành công fail được gọi. Một dịch thực tế sẽ được gần gũi hơn với:

g' :: Num b => [Maybe b] -> [b] 
g' x = x >>= \x' -> case x' of 
         Just n -> [n+1] 
         _  -> fail "pattern match exception" 

bây giờ chúng tôi có

Prelude> g' [Nothing] 
[] 

fail s hữu phụ thuộc vào đơn nguyên. Đối với các danh sách, nó cực kỳ hữu ích, về cơ bản làm cho công việc phù hợp với mô hình trong việc hiểu danh sách. Nó cũng rất tốt trong đơn Maybe, vì lỗi trùng khớp mẫu sẽ dẫn đến tính toán không thành công, chính xác là khi Maybe phải là Nothing. Đối với IO, có lẽ không quá nhiều, vì nó chỉ đơn giản là ném một ngoại lệ lỗi người dùng thông qua error.

Đó là toàn bộ câu chuyện.

+0

Tôi hiểu rằng do-notation là cú pháp đường cho (>> =) nhưng tôi chưa bao giờ thấy chính xác như thế nào. Điều đó có ý nghĩa và trông giống như tính toán lambda bình thường. Đối với phần còn lại của câu trả lời của bạn một lần nữa cảm ơn. 'Người <$> Chỉ cần tên <*> tuổi' rõ ràng là gọn gàng hơn và tôi có khả năng 'nâng' các chức năng thuần túy như vậy trong tương lai, bất kể có nghĩa là gì. Điều này đưa tôi đến phần thứ ba của câu trả lời của bạn nơi bạn giới thiệu máy biến áp mtl và monad. Tôi chưa bao giờ đọc về những điều này và không biết bất cứ điều gì về chúng như tôi luôn luôn giả định rằng họ đã xa hơn những gì tôi đã thử nghiệm. Có vẻ như tôi sẽ bắt đầu tiếp theo. – Dave

+0

Ch. 18 của Real World Haskell (http://book.realworldhaskell.org/read/monad-transformers.html) giới thiệu các biến thế đơn nguyên. LYAH tiếc là dường như không bao gồm chúng. Nâng, nói chung, có nghĩa là bạn có một cái gì đó của một số loại thường xuyên và đặt nó vào một bối cảnh fancier. Vì vậy, sử dụng '<$>' nâng một chức năng thuần túy vào một ứng dụng, và chức năng 'lift' của một biến áp đơn lẻ có tính toán trong đơn nguyên cơ bản và nâng nó thành đơn nguyên kết hợp ưa thích. –

+1

khi điều ở bên trái của '<-' là một mẫu không hoàn chỉnh, sau đó nó trở nên phức tạp hơn – newacct

4

do -blocks của biểu mẫu var <- e1; e2 desugar thành biểu thức sử dụng >>= như sau e1 >>= \var -> e2. Vì vậy, mã getPerson của bạn trở thành:

getPerson = 
    getLine >>= \name -> 
    getInt >>= \age -> 
    return $ Just Person <*> Just name <*> age 

Như bạn thấy điều này không phải là rất khác nhau từ các mã sử dụng do.

+0

Cảm ơn, tôi đã nhận thức được ký hiệu đã được sugared (>> =) nhưng không chắc chắn chính xác như thế nào. Tôi đã vẫy tay với ghci cố gắng để có được kết quả mong đợi ('tôi' mong đợi) mà không thành công. – Dave

+1

Điều này có vẻ dễ chịu hơn nhiều sau đó khi mọi người sử dụng lồng nhau lồng nhau. – Davorak

1

Trên thực tế, theo this explaination, bản dịch chính xác của mã của bạn là

getPerson = 
    let f1 name = 
        let f2 age = return $ Just Person <*> Just name <*> age 
         f2 _ = fail "Invalid age" 
        in getInt >>= f2 
     f1 _ = fail "Invalid name" 
    in getLine >>= f1 

getInt = 
    let f1 n = case n of 
       ((x,""):[]) -> return (Just x) 
       _ -> return Nothing 
     f1 _ = fail "Invalid n" 
    in (fmap reads getLine :: IO [(Int,String)]) >>= f1 

Và trận đấu hình ví dụ

f x = do 
    Just n <- x 
    [n+1] 

dịch sang

f x = 
    let f1 Just n = [n+1] 
     f1 _ = fail "Not Just n" 
    in x >>= f1 

Rõ ràng, kết quả dịch này ít có thể đọc được hơn phiên bản lambda, nhưng nó hoạt động có hoặc không có p attern phù hợp.

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