7

ここ数週間、Haskell を独学で学ぼうとしてきました。現在、コンピューターが乱数を選択し、ユーザーがそれを推測しようとする、ちょっと間抜けな推測ゲームを実装しようとしています。ユーザーが間違っている場合、プログラムはユーザーに答えが高いか低いかを伝え、ユーザーが正しく推測できるまで推測できるようにします。私はそれを機能させましたが、ユーザーがすべてのゲームで行った推測の数を追跡し、正しく推測したらその数をユーザーに報告する機能を追加したいと思います。

命令的なバックグラウンドから来ると、ユーザーが推測するたびにインクリメントされるカウンターを用意するのが自然なことですが、Haskell では実際にそれを行うことはできません (少なくとも、すべてのステートレス性と不変性のように思えます)。それを防ぎます)。

getGuess 関数と giveHints 関数に、これまでの推測の数を表す追加のパラメーター (これを numGuesses と呼びましょう) を取り、これらのメソッドを呼び出すたびに (numGuesses+1) を渡すというアイデアをいじりました。しかし、私はそれを機能させることができませんでした (言うまでもなく、それが機能するかどうかさえわかりません)。

私のコードは以下です。任意の提案をいただければ幸いです。私は主にアイデアを探していますが、実際のコードも自由に投稿してください。また、コードがうまくいかない場合や、コードを改善する方法を教えてください。

    import System.Random
    import System.IO
    import Control.Monad

    main = do
        gen <- getStdGen
        let (ans,_) = randomR (1,100) gen :: (Int,StdGen)
        putStrLn $ "I'm thinking of a number between 1 and 100..."
        getGuess ans
        putStrLn "You guessed it in __ guesses!"
        putStr "Play again? "
        hFlush stdout
        desire <- getLine
        when ((desire !! 0) `elem` ['y','Y']) $ do
            putStrLn ""
            newStdGen
            main

    getGuess ans = do   
        putStr "Your guess? "
        hFlush stdout
        guessStr <- getLine
        giveHints ans (read guessStr)

    giveHints ans guess = do
        when (ans /= guess) $ do
           if ans > guess 
               then putStrLn "It's higher." 
               else putStrLn "It's lower."
           getGuess ans

注: ライン バッファリングを使用しているため、hFlush stdout を使用しています。これがないと、一部の相互作用の順序が期待どおりにならないことがあります。

4

2 に答える 2

8

考えていたカウント方法を実際に実装することはできますが、それでも状態を明示的に渡す必要があります。しかし、この場合、それほど面倒なことではありません。実際、これはヘルパー関数でよく見られるパターンであり、実際にStateモナドを使用するのはやり過ぎです。

私がよく言及しているパターンは、次のようになります。

doStuff xs' = go xs' 0
  where
    go (x:xs) n = .. etc ..

これがコードです。

import System.Random       (randomRIO)
import Control.Applicative ((<$>))
import Control.Monad       (when)
import Text.Printf         (printf)  

playGame :: Int -> Int -> IO ()
playGame answer curGuesses = do
    putStrLn "What is your guess?"
    putStr   ">"
    guess <- getGuessFromUser
    when (guess /= answer) $ do
        giveHints answer guess
        playGame answer (curGuesses + 1)
    when (guess == answer) $ do
        putStrLn "You guessed it!"
        printf   "You guessed %d times!\n" (curGuesses + 1)

giveHints :: Int -> Int -> IO ()
giveHints answer guess 
    | answer > guess = putStrLn "It's higher!"
    | otherwise      = putStrLn "It's lower!"

getGuessFromUser :: IO Int
getGuessFromUser = do
    read <$> getLine

main :: IO ()
main = do
    answer <- randomRIO (1, 100)
    putStrLn "I'm thinking of a number between 1 and 100."
    playGame answer 0

ノート

  • <$>fmap
  • randomRIOたちはすでに IO モナドにいるので、ダニエルが述べたように使用しました。
  • 正しい出力を得るために、Windows でコマンド プロンプトを使用するhSetBuffering必要はありませんでした。hFlushただし、YMMV。
于 2012-08-08T02:23:46.510 に答える
3

推測の数に追加のパラメーターを追加することは、まさにこの種のことを機能的に行う方法です。

関数の基本的な考え方は、「何か」のさまざまな値に応じて異なる動作をする必要がある関数がある場合、その何かが関数のパラメーターであるということです。これは純粋さの単純な結果です。関数は、同じ入力に対して常に同じものを返さなければなりません。

より高度な手法に到達すると、追加のパラメーターを「隠す」さまざまな方法があり、それらを明示的に記述/渡す必要がなくなります。これは基本的にモナドが行うこととまったく同じStateであり、モナドについての 1 つの考え方は、IOモナドが何か似たようなことをしているということです。しかし、関数型プログラミングに慣れていない場合は、この考え方に慣れる方がおそらく役立つでしょう。引数を介して呼び出している関数に情報を伝達し、その引数を介して情報を受け取ります。呼び出す関数が情報を検索する (または変更する) ことがわかっている外部の場所 (カウンターの値など) に情報を残すという命令的なトリックに頼ることはできません。

于 2012-08-08T06:15:19.407 に答える