Haskell: 文字列, テキスト型

Haskell の文字列は, 歴史的な事情で, 若干複雑になっています。

Char型

1文字を表すのは Char型です。値の意味は Unicode code point です。なので、厳密には「文字」ではない。

リテラルは「'」で文字を囲みます。

Prelude> :type 'あ'
'あ' :: Char

文字リテラルでは, \x に続けて16進数で10ffffまでのcode point を指定できます。これを超える値の場合は out of range エラー。

Char型は次の型クラスのインスタンスです。

Bounded Enum Eq Data Ord Read Show Ix Storable IsChar PrintfArg

TODO: このほか data family URec のインスタンス?

String型

組込みの文字列型は String です。String[Char], つまりCharのリスト型の別名になっています。

String は素朴な1文字単位のリストなので, 想像どおり, パフォーマンスに難があります。過去との互換性のために残っています。

新しいプログラムでは, バイト列がほしいときは ByteString, 文字列がほしいときは Text データ型を使ってください。

type String = [Char] 	-- Defined in `GHC.Base'

リテラルは「"」で囲みます。String型は単なるリストなので、その値の操作については, リストに適用できる関数がそのまま使えます。

次のプログラムは, ファイルの先頭を表示します。

Haskell
[RAW]
  1. import Data.Text as DT
  2. import Data.Text.IO as DTI
  3. -- 先頭のn行を得る
  4. headNLines :: Int -> Text -> Text
  5. headNLines n cs =
  6. DT.unlines $ Prelude.take n $ DT.lines cs
  7. main :: IO ()
  8. main = do
  9. cs <- DTI.readFile "str1.hs"
  10. DTI.putStr $ headNLines 5 cs

それぞれの関数:

readFile :: FilePath -> IO String
ファイルを読み込む「アクション」を返します。ここで実際にファイルを読み込むのではなく, 必要があったときに実際に読み込まれます。
lines :: String -> [String]
文字列を行の集まり(リスト)に分割します。結果の各文字列には, 改行文字 '\n' は含まれません。

ただし, 元の文字列の末尾の改行文字は単に除去されるため, 元の文字列に厳密に復元できるようにはなっていません。

Prelude> lines "fuga\r\n"
["fuga\r"]
take :: Int -> [a] -> [a]
整数nとリストを引数に取り, 先頭のn要素を取り出します。
unlines :: [String] -> String
行の集まりを, 改行文字 '\n' をはさんで文字列にします。

doブロックについては, またページを替えて説明しますが, とりあえずここでは,

  • doブロックはインデントで範囲指定
  • doブロックのなかでは, <-キーワードで, 右辺のアクション (IO a型の値) を左辺のa型の定数に割り当てることができます。
  • doブロックの最後の文の戻り値が, そのdoブロックの値になります。

ByteStringデータ型

bytestring パッケージの Data.ByteString モジュール, ByteString データ型は, 単なるバイトの配列です。型名に〜Stringが含まれていますが, 文字列は関係ありません。

ByteString は, 次の型クラスのインスタンスです。

Eq Data Ord Read Show IsString Monoid NFData

とはいえ, 単に右から左へデータを移すようなときは, ByteString が使えます。

Haskell
[RAW]
  1. {-# LANGUAGE OverloadedStrings #-}
  2. import Data.ByteString as BS
  3. import Data.ByteString.Char8 as BSC
  4. import System.IO
  5. main :: IO ()
  6. main = do
  7. Prelude.putStr "何か入力> "
  8. hFlush stdout
  9. str <- BS.getLine
  10. BSC.putStrLn $ "str = " `BS.append` str
  11. Prelude.putStrLn $ (show $ BS.length str) ++ " bytes"

OverloadedStrings で、文字列リテラルが String型以外になりえます。IsString型クラスのインスタンス (で型が合うもの) になる。

実行例

何か入力> ああ愛
str = ああ愛
9 bytes

単にそのまま表示するだけなら, ただのバイト列なので, 化けません。この例だと, 文字数は UTF-8によるバイト数の9になっています。

Text

本物の文字列は, Text 型です。

入出力は ByteString を使い, 文字列に変換して使います。

Haskell
[RAW]
  1. import Data.Text
  2. import Data.Text.Encoding
  3. import qualified Data.ByteString
  4. countChars :: Data.ByteString.ByteString -> Int
  5. countChars s = Data.Text.length $ Data.Text.Encoding.decodeUtf8 s
  6. main = do
  7. cs <- Data.ByteString.readFile "str2.txt"
  8. print $ countChars cs

"str2.txt"ファイル (UTF-8で保存。)

あああAbc23はひふ

実行結果:

$ runhaskell str2.hs 
12

改行文字込みで, 正しく12文字とカウントできている。

Text型については、別ページに続きます; テキスト型の周辺