2017-06-19 31 views
1

從這個 https://en.wikibooks.org/wiki/Haskell/Arrow_tutorial#Hangman:_Main_program哈斯克爾箭頭教程循環/狀態

如何在IO完成?特殊

main :: IO() 
main = do 
    rng <- getStdGen 
    interact $ unlines      -- Concatenate lines out output 
     . ("Welcome to Arrow Hangman":)  -- Prepend a greeting to the output 
     . concat . map snd . takeWhile fst -- Take the [String]s as long as the first element of the tuples is True 
     . runCircuit (hangman rng)   -- Process the input lazily 
     . ("":)        -- Act as if the user pressed ENTER once at the start 
     . lines        -- Split input into lines 

交互似乎是(字符串 - >字符串) - > IO()。印象中它會打印每行讀取的函數。令我困惑的是,第一行是如何打印的。遊戲狀態在哪裏存儲?

runCircuit早些時候以其所有輸入已經生成的方式使用。我很困惑這個版本是如何逐行運行,但似乎沒有存儲任何狀態?

Circuit String (Bool, [String])如何以逐行方式運行runCircuit :: Circuit a b -> [a] -> [b]?似乎記得以前的結果在哪裏?

+3

您的問題似乎與FRP和箭頭無關,無論您使用該示例的教程如何。請相應標記。 – leftaroundabout

+0

狀態不一定要明確存儲在任何地方。它的作用與'State'完全一樣,它是一個函數 - >(a,s)'。 「國家」要簡單得多,所以不能認爲它應該更容易理解,但原則是完全一樣的。你鏈接的頁面給出了'runCircuit'死亡的簡單定義 - '\ cir - > snd。 mapAccumL uncircuit cir'。在這個定義中,它是'mapAccumL',它處理將每個先前狀態提供給後續定義。 'Circuit'本身就是一個有狀態計算的表示 - 所有的魔法都發生在'mapAccumL'裏面。 – user2407038

回答

2

Interact不會每行運行該功能。 interactrunCircuit是懶惰的。由於您將輸入分爲多行並連接輸出,因此您會在提供更多輸入時看到runCircuit的進度。

功能runCircuit定義如下:

runCircuit :: Circuit a b -> [a] -> [b] 
runCircuit _ []  = [] 
runCircuit cir (x:xs) = 
    let (cir',x') = unCircuit cir x 
    in x' : runCircuit cir' xs 

有您看到產生在輸出列表中的一個元件,用於在輸入列表中的每個元素(每行)。這已經表明你可以懶懶地處理這個列表。 (對比:如果需要的xs長度產生第一輸出x',然後runCircuit偷懶。)

讓我們把那個連同Circuit定義:

data Circuit a b = Circuit { unCircuit :: a -> (Circuit a b, b) } 

的您運行電路的方式是您提供a類型的第一個輸入x,並且不僅獲得b類型的第一個輸出x',而且還獲得延續Circuitcir' in runCircuit)。這個延續是新的Circuit a b,在下一次迭代中由runCircuit使用。這就是如何保持狀態:新的Circuit將與原來的相似,但它可能受到之前輸入的影響。

例如,您可以定義一個將Ints相加併產生總和的電路。還有的是,文章中一個例子,但爲了讓事情超簡單:

mySum :: Circuit Int Int 
mySum = mySum' 0 

mySum' :: Int -> Circuit Int Int 
mySum' acc = Circuit $ \input -> 
    let acc' = acc + input 
    in (mySum' acc', acc') 

在每次迭代中,延續Circuit返回,mySum' acc',使用acc',新的蓄能器,其中包含了總結到這一點。所以這Circuit保持國家,因爲它記住或繼續所有數字的總和,直到那一點。

返回到文章中,稍微一般的功能:

accum :: acc -> (a -> acc -> (b, acc)) -> Circuit a b 
accum acc f = Circuit $ \input -> 
    let (output, acc') = input `f` acc 
    in (accum acc' f, output) 

返回延續Circuit在源於其自身不同的元組的第一個參數。它被稱爲accum acc f,但繼續是accum acc' f,其中acc'取決於inputacc,因此它保留在此累加器中的內存。

使用continuations非常非常常見。我認爲大多數管道/流處理框架和許多FRP實現都是這樣做的,包括Yampa,Varying,Dunai和netwire。