2012-09-17 70 views
4

我只是完全與列表和單子混淆,所以也許我的問題是不正確或非常天真。 我見過的方式做它用mapM_ FUNC here以新行打印列表元素

mapM_ print [1, 2, 3, 4] 

但我不知道它究竟是如何工作的,並想知道我怎麼能在這樣的方式做到這一點:

x <- [1, 2, 3] 
print x 

,或者,如果我的理解是正確的:

[1, 2, 3] >>= print 

我明白[1,2,3]已鍵入[a]和打印的類型爲Show a => a -> IO()。我也明白,對於使用monad列表,我們需要在左側輸入List a,在右側輸入a -> List b。我對嗎? 你能幫我嗎?

UPD。感謝@MathematicalOrchid解釋mapM_是如何工作的。從我這邊我想解釋一下,真正的問題不是以不同的方式打印任何結果,而是以monad List提供的方式做一些monadic動作(因爲現在我圍繞着OpenGL的東西)。但我明白誤解的根源在於混合單子。

UPD2。感謝大家的答案。我對這個模糊的問題表示歉意。我不知道我需要什麼答案,問題是什麼。這是因爲我不瞭解一些基礎知識。所以現在很難選擇「正確的答案」,因爲每個答案都有我尋找的小小和平。我決定選擇最接近的(儘管現在不是最有用的)我想要的。

+1

'forM_'可能看起來更熟悉一些。它只是顛倒了'mapM_'的參數。嘗試'forM_ [1,2,3] print'。 – jtobin

+1

值得一提的是你正在使用兩個monad:'List'和'IO'。你的'>> ='不起作用的原因是你試圖混合這兩者。 – stusmith

+0

@stusmith:是的,我也只是得到它:) – pkuderov

回答

9

你想要什麼不能以這種方式工作,因爲你想兩個單子混合在一起:

do x <- [1,2,3] 
    print x 

具體你是混合IO[]單子。在註釋中,對於某些Monad m,所有的陳述應該具有m a的類型。但在上面的代碼中,第一條語句的類型爲[Integer],而第二條語句的類型爲IO()

爲了得到你想要的,你應該使用ListT單子轉換的影響。 Monad transformers允許按照特定的順序將單體混合在一起,並根據需要組合它們的效果。

import Control.Monad.Trans 
import Control.Monad.Trans.List 

value = do x <- ListT (return [1,2,3]) 
      lift (print x) 

這將返回一個值爲ListT IO Integer的值。要從此變壓器中獲得IO計算結果,請使用runListT。其中將返回類型爲IO [Integer]的值。這將輸出:

GHCI> runListT value 
1 
2 
3 
[(),(),()] 

這相當於mapM print [1,2,3]。要放棄清單並獲得mapM_ print [1,2,3]的效果,您可以使用Control.Monadvoid

GHCI> void . runListT $ value 
1 
2 
3 
+0

感謝ListT。我現在不打算進入monad變形金剛 - 現在還沒有成爲我的時代。但這正是我所期待的。 – pkuderov

+0

@pkuderov我很樂意提供幫助。我知道現在有點進步。但我認爲我應該加上它,因爲它總體上解決了你正在試圖解決的那種問題。 – is7s

12

你似乎有幾件事情在這裏混淆。 (特別是,列表形成一個monad,而I/O構成了一個不同的monad。)我會盡力清除這個...

首先,print函數取任何可顯示的內容並將其寫入標準輸出,緊接着換行。所以print [1, 2, 3]工作得很好,但顯然寫在一行上的一切。要在單獨的行上寫東西,我們需要爲每個項目分別調用print。到現在爲止還挺好。

map函數將函數應用於列表的每個元素。因此map print [1, 2, 3]將應用print到列表中的每個項目。 但是,結果是I/O操作列表。這並不完全是我們所追求的。我們想要執行這些操作,而不是列出它們。

要做到這一點的方法是使用>>操作符,它將兩個I/O操作鏈接在一起(假設您對結果不感興趣 - 並且打印某些內容不會返回任何有趣的內容)。因此,foldr (>>) (return())將採取您的I/O操作列表,並將其轉換爲單個I/O操作。這個功能實際上已經定義了;它被稱爲sequence

然而,map + sequence是這樣一個常見的組合,這個也已經定義;它叫做mapM_。 (也有mapM,沒有下劃線,如果你想保留結果,但打印不返回任何東西,所以沒有必要。)


現在,這就是爲什麼mapM_有效。現在你問爲什麼其他幾種方式將無法正常工作...

x <- [1, 2, 3] 
print x 

這根本不起作用。第一行是在monad列表中。但第二行是在I/O monad中。你不能那樣做。 (你會得到一個莫名其妙的類型檢查錯誤。)我也許應該指出,這是Haskell的所謂「辦符號」,和上面的片段需要在前面的do關鍵字爲它實際上是有效的語法:

do 
    x <- [1, 2, 3] 
    print x 

無論哪種方式,它仍然無法正常工作。它差不多做什麼map print [1, 2, 3]做,但不完全。 (正如我所說的,它不會鍵入檢查。)

還建議[1, 2, 3] >>= print,這等同於以前的片段。 (事實上​​,編譯器轉換前者向後者。)原來不類型檢查,這並不兩種類型的檢查,出於同樣的原因。

這有點像試圖將一個號碼添加到一個矩陣。數字是可加的東西。 Matricies是可加的東西。但是你不能添加一個到另一個,因爲它們不一樣。如果這是有道理的。

+0

非常明確的解釋!但請再提一個問題。有沒有什麼方法可以混合monads(從一個跳到另一個)?現在我看到至少有兩種方法可以「獨立」地執行某些操作 - 通過映射throgh地圖樂趣(或其單向反射)或將動作放入List monad中。我對嗎? – pkuderov

+1

@pkuderov對於某些特定的monad對'm'和'n',有一些方法可以從'm'跳轉到'n',但是列表和IO不是這樣的一對。相反,通常的技巧是創建一個新的monad,它具有使用monad變換器時要跳轉的monad的特徵。沒有monad轉換器允許你添加'IO'效果列表類的東西,但是[有一個變換器](http://hackage.haskell.org/packages/archive/logict/0.5.0.2/doc/html /Control-Monad-Logic.html#t:LogicT),允許您將不確定性(即類似列表的特徵)添加到'IO'。 –

+0

@DanielWagner謝謝 – pkuderov

5

您可以使用sequence_爲了執行IO動作:

sequence_ $ [1, 2, 3] >>= (\x -> [print x]) 

但我認爲mapM_是相當清晰的。

+0

謝謝!對於初學者mapM_不是更清晰;) – pkuderov

+0

只是不想讓任何人認爲這是一個明智的建議。儘管'序列'對於從一系列動作中做出單個動作是非常方便的。 – stusmith

+0

也,'sequence_ $ map print [1,2,3]'。 –

4

我不會準確回答你的問題,因爲我認爲這個問題本身有點誤導。特別是,使用mapM或類似的東西這裏正確的做。使用這個任務的符號只會使它更加複雜,而且我厭惡地告訴人們做的事情不是正確的事情。但是我會爲您提供一種可能更易於消化的替代方案。

如果您來自緊迫的背景(即您熟悉C,Java,Python等),那麼您可能會發現使用forM而不是mapM更容易。語法是

forM <list of things> <action to perform for each thing> 

即它就像一個for-each循環!例如:

ghci> import Control.Monad 
ghci> forM [1,2,3] print 
1 
2 
3 
[(),(),()] 

的事情的清單是[1,2,3]和要執行的操作對每個事情是print。注意最後的返回值?這是因爲每次致電print時都會返回(),並在最後收集這些內容。如果你不想讓你使用forM_,而不是forM返回值,就像這樣:

ghci> forM_ [1,2,3] print 
1 
2 
3 

你準備好祕訣是什麼?功能forMforM_只是mapMmapM_與反轉的觀點,那就是:

forM list action = mapM action list 

我常常在我的代碼使用forM因爲它提請注意:功能而非列表這往往是你想要什麼。當功能跨越多行時,它看起來更整齊。

1

這裏大概是mapM_工作的最簡單的解釋:

main = foldr1 (>>) (map print [1, 2, 3]) 

也就是說,print應用到每個列表成員和結果使用>>加盟,所以首先你

main = foldr1 (>>) [print 1, print 2, print 3] 

並在最後得到

main = print 1 >> print 2 >> print 3 

有點更精確的解釋是這樣的:

main = foldr (>>) (return()) (map print [1, 2, 3]) 

所以最後你得到

main = print 1 >> print 2 >> print 3 >> return() 

一個空列表return()部分讓功能工作 - foldr1只是崩潰的空列表以同樣的方式headtail