Yet Another Haskell 3章 (8)
3.8 Interactivity
ユーザーインタラクションに関して
キーボードからの入力を受け付ける関数(scanf, cin等)
は厳密な意味では関数ではない。
ユーザーの入力によって結果が変わってしまうからだ。
この問題は数学の圏論におけるモナドによって解決された。
モナドに関しては、5章や9章で詳しく扱う。
インタラクティブなプログラムを書くためには、doを使う。
これを使うことで、プログラムの実行順序を制御できる。
(Haskellはlazyな言語なので、通常、プログラムの実行順序は決まっていない)
ユーザーの名前を聞く簡単なプログラムが次だ。
module Main where import System.IO main = do hSetBuffering stdin LineBuffering putStrLn "Please enter your name: " name <- getLine putStrLn ("Hello, " ++ name ++ ", how are you?")
これを実行すると次のようになる。
*Main> main Please enter your name: quotientK Hello, quotientK, how are you?
確かに、インタラクションができている。
次にプログラムの解説。
import System.IOは入出力のための関数を定義しているIOというライブラリを取り込む。
doはコマンドを順番に実行するためのもの。
hSetBuffering stdin LineBufferingはGHCでの実行に必要なもので、特に重要ではないっぽい。(GHCでは通常ある一定の長さの入力を行うまで、処理が次に行かないらしい。このコマンドを実行すると処理が次に行くようになるらしいが、試しにこの行を削除して実行してみても特に問題無かった。環境がWindowsだからかな。)
putStrLnは文字列を画面に出力するコマンド。
name <- getLineは、他の言語では、name = getLine()のようなもの。
ただ、getLineは関数ではない(ユーザーの入力によって結果が代わる)ため、<-を使う。これは「getLineを実行し、その結果をnameに保存する」を意味する。
最後の行は、nameを加工して、画面に文字列を出力する。
こういうものだと思えば何のことは無いが、この後ろで働いてるモナドの理解が大変なんだろうね。まあとりあえず今は気にしないでおこう。
お次は、数字当てゲームの例。
1から100までの数字をランダムに決め、それを当てるプログラムだ。
乱数もまた純粋な関数ではないため、モナドを使う必要がある。
module Main where import System.IO import System.Random main = do hSetBuffering stdin LineBuffering num <- randomRIO (1::Int, 100) putStrLn "I’m thinking of a number between 1 and 100" doGuessing num doGuessing num = do putStrLn "Enter your guess:" guess <- getLine let guessNum = read guess if guessNum < num then do putStrLn "Too low!" doGuessing num else if guessNum > num then do putStrLn "Too high!" doGuessing num else do putStrLn "You Win!"
num <- randomRIO (1::Int, 100)は、1から100までの乱数を生成し、numに代入する。getLineと同じく<-を使用する。
- Intは生成する乱数が整数であること指定するためのもの。100の方にはいらないのは推論されるからだろう。
その次がdoGuessingを実行する。
doGuessing num = doでdoGuessingの定義が始まる。
guess <- getLineで、guessにユーザーからの入力を保存する。
let guessNum = read guessで、guessを数値に変換する。(getLineの結果はStringだから)
readは純粋な関数のため、read guessでは<-は使わない。
なお、doの中ではlet使用時にinは必要ないとのこと。
そして、guessとnumの値によって分岐し、次に続く処理を決める。
なるほど。
次は、再帰的にコマンドを実行する際の注意。
まず間違った例から。
askForWords = do putStrLn "Please enter a word:" word <- getLine if word == "" then return [] else return (word : askForWords)
間違っているのは最後の行。
askForWordsはリストではなく。アクション(action)だから、:で繋ぐことができない。
で、正しい例は、
askForWords = do putStrLn "Please enter a word:" word <- getLine if word == "" then return [] else do rest <- askForWords return (word : rest)
<-を使ってアクションの結果をrestに代入し、それを:で繋いで返すのが正しい。
returnがしれっと出てきているが、この<-やreturnがモナドの一部(?)というのは聞いたことがある。
基本的な部分は理解できた。と思う。
Exercise 3.10
module Main where import System.IO main = do hSetBuffering stdin LineBuffering nums <- scanNumbers putStrLn ("The sum is " ++ show (Main.sum nums)) putStrLn ("The product is " ++ show (Main.product nums)) printFactorialList nums scanNumbers = do putStrLn "Give me a number (or 0 to stop):" line <- getLine let n = read line if n == 0 then return [] else do rest <- scanNumbers return (n : rest) sum list = foldl (+) 0 list product list = foldl (*) 1 list factorial 1 = 1 factorial n = n * factorial(n - 1) printFactorialList [] = do return () printFactorialList (x:xs) = do putStrLn ((show x) ++ " factorial is " ++ (show (factorial x))) printFactorialList xs
実行結果
*Main> main Give me a number (or 0 to stop): 10 Give me a number (or 0 to stop): 2 Give me a number (or 0 to stop): 5 Give me a number (or 0 to stop): 1 Give me a number (or 0 to stop): 0 The sum is 18 The product is 100 10 factorial is 3628800 2 factorial is 2 5 factorial is 120
何もしないことの表し方でハマった…。return ()か。