数値データの文字列変換
数値データをファイルに保存するためには、それを文字列に変換しないといけない。また、文字列で保存されたデータは数値に変換しないと使えない。数値データの文字列化と文字列の数値化は Haskell を使いこなすための必須知識だ。
数値を文字列化する関数は show だ。また、文字列を数値化するときは read cs :: Int のように read 関数の戻り値の型を指定する必要がある。
また、リストなどの構造的なデータの場合は次のようになる。
したがって、ファイルに数値を保存したり、読み出したりするときには show と read を用いなければならない。
自分で定義した代数的データ型に show や read を使うためには、data 宣言するときに deriving ディレクティブでデータ型を Show クラスと Read クラスのインスタンス宣言しておく必要がある。
これで Haskell で処理したデータを自由にファイルに保存したり読み出したりできる。
また、自前のデータ型は data 宣言の時に Show クラスのインスタンス宣言をしておかないと、ghci のコマンドラインで表示できない。それは ghci のコマンドラインでデータを表示する時に内部的に show 関数が使われるからだ。初めて代数的データ型を定義した時によくある落とし穴だ。
そこで、まずリスト [1,2,3,4,5] を改行区切りのデータにしてファイルに保存することを考えてみる。
ファイルのデータを取り出してリストにするには次のようにする。
リストのリストの場合も簡単にできる。まずはファイルへの保存から。文章で表現するより ghci の実験結果を見てもらったほうが仕組みが分かりやすい。
次にこれをリストのリストに復元する。
データのファイル保存と読み出しをもっとプログラムらしく整形してみると次のようになる。
save_data filename datalist = do
let
datastring = unlines $ map (\x -> unwords $ map show x) datalist
writeFile filename datastring
load_data filename = do
cs <- readFile filename
let
datalist = map (map (read :: String -> Int)) $ map words $ lines cs
return datalist
ghci でテストしてみた。
数値の文字列化
Prelude> show 777
"777"
Prelude> read "777" :: Int
777
Prelude> read "777" :: Double
777.0
Prelude> read "[1,2,3]" :: [Int]
[1,2,3]
Prelude> show (1, 'a')
"(1,'a')"
Prelude> read "(1,'a')" :: (Int, Char)
(1,'a')
Prelude> writeFile "temp.txt" $ show [1,2,3]
Prelude> readFile "temp.txt"
"[1,2,3]"
Prelude> do {cs <- readFile "temp.txt"; return (read cs
:: [Int])}
[1,2,3]
Show クラスと Read クラス
Prelude> data MyData = MyData Int Double deriving (Show, Read)
Prelude> show $ MyData 1 2
"MyData 1 2.0"
Prelude> show (MyData 1 2)
"MyData 1 2.0"
Prelude> read "MyData 1 2.0" :: MyData
MyData 1 2.0
Prelude> data NewType = NewType Int
Prelude> NewType 1
:66:1:
No instance for (Show NewType) arising from a use of ‘print’
In a stmt of an interactive GHCi command: print it
Prelude> data NewType = NewType Int deriving Show
Prelude> NewType 1
NewType 1
数値データのファイルとのやりとり
数値データをファイルに保存したり読み出したりするには、データを文字列にしたり、文字列を数値化したりなどの操作の他に、空白や改行で区切られた文字列を切り分ける操作も必要だ。後者については Haskell には words, unwords, lines, unlines などの関数が用意されている。
Prelude> words "apple orange lemon"
["apple","orange","lemon"]
Prelude> unwords ["apple","orange","lemon"]
"apple orange lemon"
Prelude> lines "hello\nworld"
["hello","world"]
Prelude> unlines ["hello","world"]
"hello\nworld\n"
Prelude> writeFile "temp.txt" $ unlines $ map show
[1,2,3,4,5]
Prelude> readFile "temp.txt"
"1\n2\n3\n4\n5\n"
Prelude> do cs <- readFile "temp.txt"; return $ map (\x->read x :: Int) $
lines cs
[1,2,3,4,5]
Prelude> map (map show) mydata
[["1","2","3"],["4","5","6","7"],["8","9"]]
Prelude> map (\x -> unwords $ map show x) mydata
["1 2 3","4 5 6 7","8 9"]
Prelude> unlines $ map (\x -> unwords $ map show x) mydata
"1 2 3\n4 5 6 7\n8 9\n"
Prelude> writeFile "temp.txt" $ unlines $ map (\x -> unwords $
map show x) mydata
Prelude> readFile "temp.txt" >>= putStr
1 2 3
4 5 6 7
8 9
Prelude> do cs <- readFile "temp.txt"; print cs
"1 2 3\n4 5 6 7\n8 9\n"
Prelude> do cs <- readFile "temp.txt"; print $ lines cs
["1 2 3","4 5 6 7","8 9"]
Prelude> do cs <- readFile "temp.txt"; print $ map words $ lines cs
[["1","2","3"],["4","5","6","7"],["8","9"]]
Prelude> do cs <- readFile "temp.txt"; print $ map (map (read :: String
-> Int)) $ map words $ lines cs
[[1,2,3],[4,5,6,7],[8,9]]
module SaveData where
Prelude> :l SaveData
[1 of 1] Compiling SaveData ( SaveData.hs, interpreted )
Ok, modules loaded: SaveData.
*SaveData> save_data "temp.txt" [[1,2,3],[4,5]]
*SaveData> load_data "temp.txt"
[[1,2,3],[4,5]]