0

私は Haskell でモナドを使う練習をしたかったIOので、コンソールへの出力中に無限に再帰する「スクリーンセーバー」プログラムを作ることにしました。コードが実行されると、コンソールには何も表示されません。プログラムに送信するSIGTERMと、ハードコードされた「概念実証」draw出力が出力されますが、無限再帰(go関数)からの出力は出力されません。

これは、関数内のコンソールに出力するコードが呼び出されないという遅延評価と関係があると思われますが、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

3 に答える 3

4

関数内のこの行go:

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

実際には IO アクションを実行しません。既存の 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 アクションです。
于 2014-01-27T00:14:30.827 に答える
1

大まかな一般的な規則として、IO値は通常、関数矢印1の右側にのみ上向きにする必要があります。あなたがモナドについてどれだけ読んだかわかりません... Haskell がモナドに対して行うことは、他の何よりもKleisli の矢A -> M Bであることに言及するのは良いかもしれません。AB

それは今あなたの質問に本当に答えているわけではありませんが、それに応じてプログラムをリファクタリングすれば(とにかく練習が必要だと思います)、うまくいくと思うので、このままにしておきます。あなたのコードは私には少し広すぎて、詳細に時間を割く余裕がありません...


1もちろん、この規則には例外があります。実際には、いくつかの非常に重要なもの (一般的なアクション コンビネータ、ループなど) があります。しかし、それらはほとんどなく、標準モジュールで既に定義されていますControl.Monad

于 2014-01-26T23:43:06.367 に答える
0

goIO決して評価されないアクションのリストを取ります。についても同じです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とにかくすぐに評価するので、 draw が を取る理由はありません。

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アクションは実行されませんでした。

于 2014-01-27T01:12:37.450 に答える