- ToDoリスト
- 乱数
- ByteString
- 演習
- todo.txt に1行ずつタスクを記録
- アイテムの追加 (appendtodo.hs)
- アイテムの削除 (deletetodo.hs)
import System.IO
main = do
todoItem <- getLine
appendFile "todo.txt" (todoItem ++ "\n")
- getLine :: IO String
- todoItem :: String
- appendFile :: FilePath -> String -> IO ()
- todo.txt の読み込み
- ToDoリストの表示
- 番号の入力
- アイテムを削除
- 一時ファイルに新しいToDoリストを書き込む
- todo.txtの削除
- 一時ファイルのファイル名をtodo.txtに変更
import System.IO
import System.Directory
import Data.List
import Control.Exception
main = do
contents <- readFile "todo.txt"
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
mapM_ putStrLn numberedTasks
putStrLn "Which one do you want to delete?"
hFlush stdout
numberString <- getLine
let number = read numberString
newTodoItems = unlines $ delete (todoTasks !! number) todoTasks
(tempName, tempHandle) <- openTempFile "." "temp"
hPutStr tempHandle newTodoItems
hClose tempHandle
removeFile "todo.txt"
renameFile tempName "todo.txt"
main = do
contents <- readFile "todo.txt"
let todoTasks = lines contents
- readFile "todo.txt" :: IO String
- contents :: String
- contents = "Iron the dishes\nDust the dog\nTake salad out of the oven"
- lines :: String -> [String]
- todoTasks = [Iron the dishes,Dust the dog,Take salad out of the oven]
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line) [0..] todoTasks
mapM_ putStrLn numberedTasks
- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
この場合は (Integer -> String -> String) -> [Integer] -> [String] -> [String] - todo.txt が何行あるのか気にしなくていい
- numberedTasks = [ "0 - Iron the dishes", "1 - Dust the dog", "2 - Take salad out of the oven"]
putStrLn "Which one do you want to delete?"
hFlush stdout
numberString <- getLine
let number = read numberString
newTodoItems = unlines $ delete (todoTasks !! number) todoTasks
- read :: Read a => String -> a
- numberの型は?
- readLn :: Read a => IO a という関数もある
- unlines :: [String] -> String
- delete :: Eq a => a -> [a] -> [a]
delete 1 [1,2,3,1,2] = [2,3,1,2]
(tempName, tempHandle) <- openTempFile "." "temp"
hPutStr tempHandle newTodoItems
hClose tempHandle
removeFile "todo.txt"
renameFile tempName "todo.txt"
- openTempFile :: FilePath -> String -> IO (FilePath, Handle)
最後にランダムな文字をいくつか付けて temp4600 みたいなファイルを作る
現在の実装だと途中でエラーが起こったときに,一時ファイルが残ってしまう場合がある. 例えば,numberにリストの長さ以上の値を入力すると一時ファイルが残る.
Control.Exception の braketOnError を使う
bracketOnError (openTempFile "." "temp")
(\(tempName, tempHandle) -> do
hClose tempHandle
removeFile tempName)
(\(tempName, tempHandle) -> do
hPutStr tempHandle newTodoItems
hClose tempHandle
removeFile "todo.txt"
renameFile tempName "todo.txt")
- bracketOnError :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
- bracket 処理が終わると常にリソースを開放する
- bracketOnError 例外が発生した時のみリソースを開放
import System.Environment
import Data.List
main = do
args <- getArgs
progName <- getProgName
putStrLn "The arguments are:"
mapM_ putStrLn args
putStrLn "The program name is:"
putStrLn progName
- getArgs :: IO [String]
- getProgName :: IO String
- todo.txt だけでなく別のファイル名にも対応させる
- 以下の3つの操作に対応させる
- タスクの閲覧
- タスクの追加
- タスクの削除
$ ./todo view todo.txt
$ ./todo add todo.txt "Find the magic sword of power"
$ ./todo remove todo.txt 2
import System.Environment
import System.Directory
import System.IO
import Data.List
dispatch :: String -> [String] -> IO ()
dispatch "add" = add
dispatch "view" = view
dispatch "remove" = remove
main = do
(command:argList) <- getArgs
dispatch command argList
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
view :: [String] -> IO ()
view [fileName] = do
contents <- readFile fileName
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line)
[0..] todoTasks
putStr $ unlines numberedTasks
remove :: [String] -> IO ()
remove [fileName, numberString] = do
contents <- readFile fileName
let todoTasks = lines contents
numberedTasks = zipWith (\n line -> show n ++ " - " ++ line)
[0..] todoTasks
putStrLn "These are your TO-DO items:"
mapM_ putStrLn numberedTasks
let number = read numberString
newTodoItems = unlines $ delete (todoTasks !! number) todoTasks
bracketOnError (openTempFile "." "temp")
(\(tempName, tempHandle) -> do
hClose tempHandle
removeFile tempName)
(\(tempName, tempHandle) -> do
hPutStr tempHandle newTodoItems
hClose tempHandle
removeFile fileName
renameFile tempName fileName)
ファイルとタスク番号を受け取り, そのタスクをToDoリストの先頭に持ってくるbump関数を実装してみましょう.
bump :: [String] -> IO ()
bump [fileName, numberString] = do
contents <- readFile fileName
let todoTasks = lines contents
number = read numberString
(xs, y:ys) = splitAt number todoTasks
newTodoItems = unlines $ y:xs ++ ys
bracketOnError (openTempFile "." "temp")
(\(tempName, tempHandle) -> do
hClose tempHandle
removeFile tempName)
(\(tempName, tempHandle) -> do
hPutStr tempHandle newTodoItems
hClose tempHandle
removeFile "todo.txt"
renameFile tempName "todo.txt")
dispatch :: String -> [String] -> IO ()
dispatch "add" = add
dispatch "view" = view
dispatch "remove" = remove
dispatch command = doesntExist command
doesntExist :: String -> [String] -> IO ()
doesntExist command _ =
putStrLn $ unwords ["The", command, "command doesn't exist"]
add :: [String] -> IO ()
add [fileName, todoItem] = appendFile fileName (todoItem ++ "\n")
add _ = putStrLn "The add command takes exactly two arguments"
view,removeに対しても同様
- $ ./todo
(command:argList) <- getArgs でエラーが起きる - 存在しないファイル名を指定
- Haskellは 参照透明
- 何回呼んでも同じ結果を返す
randomNumber :: Int
randomNumber = 4
- 乱数はどうするのか?
random :: RandomGen g => g -> (a, g)
- g 乱数のジェネレータ
- a 乱数
- 乱数のジェネレータの作り方
- StdGen型を使う(RandomGenのインスタンスになってる)
- data StdGen = StdGen Int32 Int32
- mkStdGen :: Int -> StdGen
ghci> random (mkStdGen 100)
<interactive>:3:1:
Ambiguous type variable `a0' in the constraint:
(Random a0) arising from a use of `random'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: random (mkStdGen 100)
In an equation for `it': it = random (mkStdGen 100)
型を書かないと怒られます
ghci> random (mkStdGen 100) :: (Int, StdGen)
(-1352021624,651872571 1655838864)
ghci> random (mkStdGen 949494) :: (Int, StdGen)
(539963926,466647808 1655838864)
ghci> random (mkStdGen 949488) :: (Float, StdGen)
(0.8241101,1597344447 1655838864)
ghci> random (mkStdGen 949488) :: (Bool, StdGen)
(False,1485632275 40692)
ghci> random (mkStdGen 949488) :: (Integer, StdGen)
(1691547873,1597344447 1655838864)
- 3回コイントスする関数
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
let (firstCoin, newGen) = random gen
(secondCoin, newGen') = random newGen
(thirdCoin, newGen'') = random newGen'
in (firstCoin, secondCoin, thirdCoin)
- ジェネレータを変えないと (True, True, True) か (False, False, False) のどちらかの結果にしかならない
ghci> threeCoins (mkStdGen 21)
(True,True,True)
ghci> threeCoins (mkStdGen 22)
(True,False,True)
ghci> threeCoins (mkStdGen 943)
(True,False,True)
ghci> threeCoins (mkStdGen 944)
(True,True,True)
- 今回は型注釈がなくても大丈夫
randoms :: RandomGen g => g -> [a]
randoms gen = (\(value, newGen) -> value : randoms newGen) (random gen)
- 新しいジェネレータは返せない
- randoms :: RandomGen g => g -> ([a], g) ではない
- 無限リストを返すとき,gとして何を返すのか?
- 有限リストならジェネレータを返せる
finiteRandoms :: (RandomGen g, Random a, Num n) => n -> g -> ([a], g)
finiteRandoms 0 gen = ([], gen)
finiteRandoms n gen =
let (value, newGen) = random gen
(restOfList, finalGen) = finiteRandoms (n-1) newGen
in (value:restOfList, finalGen)
randomR :: RandomGen g => (a, a) -> g -> (a, g)
randomRs :: RandomGen g => (a, a) -> g -> [a]
ghci> randomR (1,6) (mkStdGen 359353)
(6,1494289578 40692)
ghci> randomR (1,6) (mkStdGen 35935335)
(3,1250031057 40692)
ghci> take 10 $ randomRs ('a','z') (mkStdGen 3)
"ndkxbvmomg"
- 今までのプログラムだと毎回同じ乱数を生成してしまう
import System.Random
main = do
gen <- getStdGen
putStrLn $ take 20 (randomRs ('a','z') gen)
$ ./random_string
guqlrxsyismobhollrmp
$ ./random_string
qznahaxkoilnnympgtmn
$ ./random_string
fzihvweqmegurclnefsn
import System.Random
main = do
res <- sequence [getStdGen, getStdGen, newStdGen, getStdGen, getStdGen]
mapM_ print res
$ runghc random_string2
323060062 1
323060062 1
1221755171 2147483398
323060063 40692
323060063 40692
- getStdGen
グローバル乱数ジェネレータの値を読む - newStdGen
- グローバル乱数ジェネレータの値をリセットする
- 新しいジェネレータの値を返しているわけではない
import System.Random
import System.IO
import Control.Monad (when)
main = do
gen <- getStdGen
askForNumber gen
askForNumber :: StdGen -> IO ()
askForNumber gen = do
let (randNumber, newGen) = randomR (1,10) gen :: (Int, StdGen)
putStrLn "Which number in the range from 1 to 10 am I thinking of? "
hFlush stdout
numberString <- getLine
when (not $ null numberString) $ do
let number = read numberString
if randNumber == number
then putStrLn "You are correct!"
else putStrLn $ "Sorry, it was " ++ show randNumber
hFlush stdout
askForNumber newGen
./guess_the_number
Which number in the range from 1 to 10 am I thinking of?
1
Sorry, it was 10
Which number in the range from 1 to 10 am I thinking of?
1
Sorry, it was 2
Which number in the range from 1 to 10 am I thinking of?
1
You are correct!
Which number in the range from 1 to 10 am I thinking of?
- "haha"のような入力した場合,readが失敗してエラーになる
- reads :: Read a => ReadS a
- type ReadS a = String -> [(a, String)]
import System.Random
import System.IO
import Control.Monad (when)
main = do
gen <- getStdGen
askForNumber gen
let (randNumber, _) = randomR (1,10) gen :: (Int, StdGen)
putStrLn "Which number in the range from 1 to 10 am I thinking of? "
hFlush stdout
numberString <- getLine
when (not $ null numberString) $ do
let number = read numberString
if randNumber == number
then putStrLn "You are correct!"
else putStrLn $ "Sorry, it was " ++ show randNumber
hFlush stdout
newStdGen
main
- 最初の実装のほうがよい
- main関数のやることが少ない
- 関数の再利用も簡単
- HaskellのStringはCharのリスト
- メモリを食う (1文字で5ワード)
- 遅延評価によるオーバーヘッド
- ByteString
- 効率のよい文字列処理
-- 正格ByteString
data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
{-# UNPACK #-} !Int -- offset
{-# UNPACK #-} !Int -- length
-- 遅延ByteString
data ByteString = Empty | Chunk {-# UNPACK #-} !S.ByteString ByteString
- 正格ByteString
- 配列になっている
- 無限の正格ByteStringは作れない
- 遅延ByteString
- 64Kバイトの正格ByteStringのリスト
- 64KバイトはCPUのL2キャッシュにフィットする
- インポートは修飾子付きで
- import qualified Data.ByteString.Lazy as B
- import qualified Data.ByteString as S
- ByteStringへの変換
- pack :: [Word8] -> ByteString
- unpack :: ByteString -> [Word8]
- Word8`は8ビットの符号なし整数
ghci> B.pack [99,97,110]
Chunk "can" Empty
ghci> B.pack [98..120]
Chunk "bcdefghijklmnopqrstuvwx" Empty
ghci> let by = B.pack [98,111,114,116]
ghci> by
Chunk "bort" Empty
ghci> B.unpack by
[98,111,114,116]
- toChunks :: ByteString -> [ByteString]
遅延BytesStringを正格ByteStringのリストに - fromChunks :: [ByteString] -> ByteString
正格ByteStringのリストを遅延ByteStringに
- cons :: Word8 -> ByteString -> ByteString
- B.cons は o(1)
- S.cons は o(n)
- readFile :: FilePath -> IO ByteString
- 正格ByteStringだとファイル内容のすべてがメモリ上に1度に読み込まれる
import System.Environment
import System.Directory
import System.IO
import Control.Exception
import qualified Data.ByteString.Lazy as B
main = do
(fileName1:fileName2:_) <- getArgs
copy fileName1 fileName2
copy source dest = do
contents <- B.readFile source
bracketOnError (openTempFile "." "temp")
(\(tempName, tempHandle) -> do
hClose tempHandle
removeFile tempName)
(\(tempName, tempHandle) -> do
B.hPutStr tempHandle contents
hClose tempHandle
renameFile tempName dest)
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy as B
main = B.putStrLn "Hello, World!"
{-# LANGUAGE OverloadedStrings, ViewPatterns #-}
import qualified Data.ByteString.Lazy as B
main = do
B.putStrLn $ f "Hello, World!"
B.putStrLn $ g "Hello, World!"
-- パターンガード
f bs
| Just (c, bs') <- B.uncons bs = bs'
| otherwise = bs
-- ViewPatterns
g (B.uncons -> Just (c, bs')) = bs'
g bs = bs
- String
- 使いやすい(パターンマッチとか)
- 大きいデータには向かない
- ByteString
- 効率がいい
- 正格ByteStringと遅延ByteStringを使い分ける
- 言語拡張を使えば楽に扱える
- ToDoリスト
- ファイルの入出力
- コマンドライン引数
- 乱数
- ByteString