Haskell Quiz No.15

難易度: λ

葉にだけ値を持つような二分木を定義してみてください!

図で書くとこんな感じです。

木の図

答えは次回

この図は mermaid というツールを使って書きました。

はじめに

前回の問題と答えは以下の通りです。

問題

難易度: λ

utf8.txtshift-jis.txt はそれぞれ utf8shift-jis で保存されたテキストファイルです。

bad :: IO ()
bad = readFile "./shift-jis.txt" >>= putStrLn

good :: IO ()
good = readFile "./utf8.txt" >>= putStrLn

上記のプログラムを実行すると、こんな感じでエラーになってしまいます。

*Main> good
はすける

*Main> bad
*** Exception: ./shift-jis.txt: hGetContents: invalid argument (invalid byte sequence)

shift-jis で保存されたテキストファイルを読むにはどうしたら良いでしょうか?

こたえ

色々ありそうですが、ここでは hSetEncoding を使う方法を解答の一例としておきます。

ここでは Windows で保存された shift-jis を扱うために cp932 にしていますが、通常の shift-jis であれば mkTextEncoding "shift-jis" で大丈夫です。(たぶん)

#!/usr/bin/env stack
-- stack script --resolver lts-12.2

import System.IO

main :: IO ()
main = do
  h <- openFile "./shift-jis.txt" ReadMode
  cp932 <- mkTextEncoding "cp932"
  hSetEncoding h cp932
  content <- hGetContents h

  putStrLn content
$ ./Ans1.hs
はすける

extra パッケージの readFileEncoding を使えばもっとすっきり書くことができます。

#!/usr/bin/env stack
-- stack script --resolver lts-12.2

import System.IO.Extra

main :: IO ()
main = do
  cp932 <- mkTextEncoding "cp932"
  content <- readFileEncoding cp932 "./shift-jis.txt"
  putStrLn content

解説

理解を深めるために readFile 関数がどのように定義されているか確認してみましょう!

readFile :: FilePath -> IO String
readFile name = do
  h <- openFile name ReadMode
  hGetContents h

なるほど。ReadMode でファイルハンドルを取得し、hGetContents でファイルの内容を返しているようです。

解答例ではこの処理の間に mkTextEncodinghSetEncoding を使った処理を挟んでいましたね。

  cp932 <- mkTextEncoding "cp932"
  hSetEncoding h cp932

この処理によって、shift-jis で保存されたファイルの内容を String 型の文字列として扱うことができるようになります。

readFileEncoding

extra パッケージの readFileEncoding の定義は以下のようになっています。

readFileEncoding :: TextEncoding -> FilePath -> IO String
readFileEncoding enc file = do
  h <- openFile file ReadMode
  hSetEncoding h enc
  hGetContents h

便利ですね!

まとめ

  • mkTextEncodingTextEncoding 型の値を作ることができる
  • hSetEncoding を利用すればエンコードを指定してファイルの内容を読み込むことができる
  • extra パッケージで提供されている readFileEncoding はそれらの処理を良い感じにまとめてくれる

実際の開発において readFile のような (getContents 系の関数) を使うことは少ないと思います。しかし、何か動くものを Haskell で作ってみたいと思っている人は、とりあえず気にせず使いましょう。(ストリーミングライブラリの知識があればそっちを使った方が良いです)

Haskell はリファクタリングの得意な言語ですから、問題として認識したときに修正すれば良いと思います。

IO の処理については Haskell入門 関数型プログラミング言語の基礎と実践 の4章, Read World Haskell の7章で詳しく解説されているので、興味を持った方はそちらも参照してみると良いかもしれません。

以上です。