0

我想在Haskell中練習使用IO monad,所以我決定製作一個「屏幕保護程序」程序,在打印到控制檯時無限遞歸。當代碼運行時,控制檯上不顯示任何內容。當我將SIGTERM發送到程序時,它會打印硬編碼的「概念驗證」draw輸出,但不會輸出無限遞歸(go函數)。爲什麼我的Haskell程序從不打印到控制檯?

我懷疑這與延遲評估有關,輸出到控制檯的代碼在go函數中永遠不會被調用,但我不知道如何解決它。任何建議將不勝感激!

Haskell代碼:

import Data.Maybe (isJust, fromJust) 
import System.Random 
import System.Console.ANSI 
import qualified System.Console.Terminal.Size as Term 

data RainDrop a = RainDrop 
    { row :: !a 
    , col :: !a 
    , count :: !a 
    } deriving (Read,Show) 

main :: IO() 
main = do 
    clearScreen 
    -- proof that draw works 
    c <- applyX 10 draw (return (RainDrop 0 2 10)) 
    go [return (RainDrop 0 0 10)] 

applyX :: Int -> (a -> a) -> a -> a 
applyX 0 _ x = x 
applyX n f x = applyX (n-1) f (f x) 

go :: [IO (RainDrop Int)] -> IO() 
go []  = return() 
go (x:xs) = do 
    prng <- newStdGen 
    go $ map draw $ maybeAddToQueue prng (x:xs) 

maybeAddToQueue :: RandomGen g => g -> [IO (RainDrop Int)] -> [IO (RainDrop Int)] 
maybeAddToQueue _ []  = [] 
maybeAddToQueue prng (x:xs) = 
    let 
    (noNewDrop, gen0) = randomR (True,False) prng 
    in 
    if noNewDrop 
    then x:xs 
    else (
     do 
     (colR,gen1) <- randomCol gen0 
     return $ RainDrop 0 colR $ fst $ randomLen gen1 
    ):x:xs 

randomCol :: RandomGen g => g -> IO (Int, g) 
randomCol prng = do 
    w <- Term.size >>= (\x -> return . Term.width $ fromJust x) 
    return $ randomR (0,(w-1)) prng 

randomLen :: RandomGen g => g -> (Int, g) 
randomLen = randomR (4,32) 

draw :: IO (RainDrop Int) -> IO (RainDrop Int) 
draw rain = do 
    x <- rain 
    prng <- newStdGen 
    setCursorPosition (row x) (col x) 
    putChar . toGlyph $ fst $ randomR range prng 
    return (RainDrop (succ $ row x) (col x) (count x)) 

toGlyph x 
| isJust a = fromJust a 
| otherwise = x 
where a = lookup x dictionary 

dictionary = 
    let (a,b) = range 
    in zip [a..b] encoding 

encoding = 
    let (a,b) = splitAt 16 katakana 
     (c,d) = splitAt 7 b 
    in a ++ numbers ++ C++ ['A'..'Z'] ++ d 

range = (' ','~') 
katakana = ['・'..'゚'] 
numbers = "012Ƹ߈Ƽ6ߖȣ9" 

回答

4

這條線在go功能:

go $ map draw $ maybeAddToQueue prng (x:xs) 

實際上並不執行任何IO操作的 - 它僅僅是創建從現有的新的IO操作。

下面是我將如何處理這個問題的一些類型簽名:

type World = [Raindrop] 

-- draw the raindrops 
draw :: World -> IO() 

-- advance the drops 
step :: World -> World 

-- add more drops 
moreRain :: World -> IO (World) 

-- the main loop 
loop :: World -> IO() 
loop drops = do 
    draw drops 
    let drops' = step drops 
    drops'' <- moreRain drops' 
    -- delay for a while here??? 
    loop drops'' 

注:

  • 我已經聲明step是假設一個純函數的滴的運動是確定性的
  • moreRain但是需要使用隨機數發生器,所以它是一個IO動作
+0

非常感謝您的設計見解。我主要是讓它工作。當*不*使用'threadDelay'減慢輸出時,它看起來很糟糕。當使用'threadDelay'時,輸出被阻塞,直到發送'SIGTERM'。 –

+0

@awashburn也許你可以用'threadDelay'問題來打開另一個問題。 –

+0

@DanielWagner如果我解決不了,我會的。 –

1

作一個粗略的一般規則:IO值通常應轉起來才功能箭頭的權利。我不知道你已經讀過多少單子......可能很好的一提,Haskell對monad的作用比其他任何更多,所以典型的簽名是A -> M B的形式,「純」 AB

現在並沒有真正回答你的問題,但是如果你相應地重構你的程序(我想你還是想要這個練習),我懷疑它會起作用,所以我會像這樣離開它;你的代碼是有點太廣闊了我承受的時候通過它在細節去...


當然也有例外的這一規則,其實一些非常重要的 - 一般的動作組合子,循環等。但這些很少,並已在標準模塊Control.Monad中定義。

0

go需要一個IO行動,這是從來沒有評估,因爲你永遠不會要求他們的名單。相同的maybeAddToQueue。相反,你可能想在你去時評估它們。

您正在構建一個應該做某事的無限循環。你可以把它歸結爲forever someAction

而且,你正在做的一切都在IO這樣你就可以使用IO版本的隨機函數:

randomLen :: IO Int 
randomLen = randomRIO (4,32) 

首先修改抽獎:

draw :: RainDrop Int -> IO (RainDrop Int) 
draw x = do 
    setCursorPosition (row x) (col x) 
    g <- randomRIO range 
    putChar $ toGlyph g 
    return (RainDrop (succ $ row x) (col x) (count x)) 

沒有理由抽獎拿IO RainDrop,因爲你馬上評估它。

maybeAddToQueue :: [RainDrop Int] -> IO [RainDrop Int] 
maybeAddToQueue [] = return [] 
maybeAddToQueue xs = do 
    noNewDrop <- randomIO 
    if noNewDrop 
    then return xs 
    else do 
     colR <- randomCol 
     len <- randomLen 
     return $ (RainDrop 0 colR len):xs 

最後,您go功能:

go :: [RainDrop Int] -> IO() 
go [] = return() 
go a = do 
    b <- maybeAddToQueue a 
    c <- mapM draw b 
    go c 

或者替代形式,這使得它非常清晰的所發生的事情:

import Control.Monad ((>=>)) 

go = maybeAddToQueue >=> mapM draw >=> go 

注意map成爲mapM。這確保您的行爲確實正在執行。由於該列表的值從不需要,因此只需使用map就不會評估列表中的任何元素,因此IO操作都不會運行。