2011-08-29 19 views
11

我玩弄組合的失敗寫,併成功地寫與簽名如何離不開符號

getPerson :: IO (Maybe Person) 

,當一個人是一個函數:

data Person = Person String Int deriving Show 

它的工作原理,我已經在DO-符號寫,如下所示:

import Control.Applicative 

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

其中

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

我寫這個函數的目的是創建可組合的可能失敗。儘管除了Maybe和IO之外,我對Monad的使用經驗不多,但如果我的數據類型更復雜且字段更多,則鏈接計算不會很複雜。

我的問題是我將如何重寫這個沒有標誌?由於我無法將名稱或年齡等名稱綁定到值,所以我不確定從哪裏開始。提問的原因僅僅是爲了提高我對(>> =)和(< *>)的理解以及編寫失敗和成功(而不是用難以辨認的單行程來謎語)。

編輯:我想我應該澄清一下,「我應該如何重寫getPerson而不使用符號」,我不關心getInt函數的一半。

+2

另請參見:[Haskell 2010 Report> Expressions#Do Expressions](http://www.haskell.org/onlinereport/haskell2010/haskellch3.html#x8-470003.14) –

+1

只是爲了挑選,請注意'getPerson' isn是一個函數,因爲它的類型簽名中沒有' - >';如果你想要一個比「價值」更精確的名字,我會去「IO行動」。請參閱[「Haskell中的所有函數」](http://conal.net/blog/posts/everything-is-a-function-in-haskell)瞭解更多信息。 –

回答

20

執行 - 符號desugars至(>> =)語法以這種方式:

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)) 

在DO-符號的每一行,所述第一後,被轉換成一個lambda其然後結合至前一行。這是將值綁定到名稱的完全機械過程。我不明白如何使用符號或不會影響組合性;這完全是語法問題。

你的另一個功能是相似的:

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 

的方向有幾個指針你似乎是領導:

當使用Control.Applicative,通常很有用<$>解除純函數放入單子。還有在最後一行的一個很好的機會:

Just Person <*> Just name <*> age 

成爲

Person <$> Just name <*> age 

此外,你應該看看單子變壓器。 mtl軟件包是最普遍的,因爲它帶有Haskell平臺,但還有其他選項。 Monad變換器允許您創建一個新的Monad,並具有底層monads的組合行爲。在這種情況下,您正在使用IO (Maybe a)類型的函數。的MTL(實際上是一個基礎庫,變壓器)限定

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

這是一樣的,你正在使用的類型,與所述m變量在IO實例化。這意味着你可以這樣寫:

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正是除MaybeT構造相同。基本上,任何時候你有一個m (Maybe a)你可以把它包裝在MaybeT創建一個MaybeT m a。這可以獲得更簡單的組合性,正如您可以通過getPerson3的新定義所看到的那樣。這個函數根本不用擔心失敗,因爲它全部由MaybeT管道處理。剩下的一件是getLine,這只是一個IO String。這通過函數lift提升到MaybeT monad中。

編輯 newacct的評論建議我應該提供一個模式匹配的例子;這與一個重要的例外情況基本相同。考慮這個例子(列表單子是我們感興趣的單子,Maybe就在那裏進行模式匹配):

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] 

這裏g不完全一樣的東西爲f,但如果模式匹配失敗?

Prelude> f [Nothing] 
[] 

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

發生了什麼事?這種特殊情況是Haskell最大的疣(IMO)之一,Monad類的fail方法。在註釋中,當模式匹配失敗時調用fail。一個實際的翻譯將接近:

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

現在我們有

Prelude> g' [Nothing] 
[] 

fail有用性依賴於單子。對於列表來說,它非常有用,基本上使列表解析中的模式匹配工作成爲可能。它在Maybe monad中也很好,因爲模式匹配錯誤會導致計算失敗,這正是Maybe應該是Nothing。對於IO,可能不是那麼多,因爲它只是通過error引發用戶錯誤異常。

這就是完整的故事。

+0

我明白do notation是(>> =)的語法糖,但我從來沒有見過如何。這是有道理的,看起來像普通的lambda微積分。至於你的其他答案再次感謝。 'Person <$>只要名字<*>年齡'顯然更加整潔,我可能'提升'純淨的功能在未來,無論電梯意味着什麼。 這將我帶到你介紹mtl和monad變形金剛的答案的第三部分。我從來沒有讀過這些,也不瞭解他們,因爲我一直認爲他們距離我正在試驗的地方更遠。看來我會在下一步開始。 – Dave

+0

Ch。真實世界中的Haskell 18(http://book.realworldhaskell.org/read/monad-transformers.html)介紹了monad變換器。不幸的是,LYAH似乎沒有涵蓋他們。總的來說,舉重意味着你採取了一些常規類型的東西,並將其放在更加有趣的環境中。因此,使用'<$>'將純函數提升爲應用程序,並且monad變換器的「提升」函數在底層monad中進行計算並將其提升到花哨的組合monad中。 –

+1

當'<-'左側的東西是一個不完整的模式,那麼它變得更加複雜 – newacct

4

do -blocks形式var <- e1; e2 desugar的使用>>=如下e1 >>= \var -> e2表達式。所以,你的getPerson代碼變爲:

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

正如你看到的,這是不使用do代碼非常不同。

+0

謝謝,我知道do-notation是加糖(>> =),但不知道如何。我正在甩掉ghci試圖獲得預期的('我的'預期)結果而沒有成功。 – Dave

+1

當人們使用嵌套parens時,這看起來更令人愉快。 – Davorak

1

實際上,根據this explaination,你的代碼的確切翻譯是

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 

和模式匹配例子

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

翻譯成

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

顯然,這個翻譯結果比lambda版本更不可讀,但它可以使用或不使用p實習生配對。