TextとByteString
はじめに
Haskell のデフォルトの文字列型は、割と愚直な、文字の連結リストであり、小さい識別子に使う場合は全く問題ないですが、大量のデータを処理するのには向いていません。
type String = [Char]
よりパフォーマンスに敏感な場合、テキストデータを処理するライブラリが2つあります。text
と bytestring
です。-XOverloadedStrings
拡張を付ければ、文字列リテラルを明示的なパッキングを行うことなくオーバーロードし、文字列リテラルとして Haskell のソースに書き、型クラス IsString
を通してオーバーロードすることができます。
class IsString a where
fromString :: String -> a
例えば:
λ: :type "foo"
"foo" :: [Char]
λ: :set -XOverloadedStrings
λ: :type "foo"
"foo" :: IsString a => a
Text
Text型はユニコード文字をいくつかパックした何かです。
pack :: String -> Text
unpack :: Text -> String
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T
-- pack から
myTStr1 :: T.Text
myTStr1 = T.pack ("foo" :: String)
-- オーバーロードした文字列リテラルから
myTStr2 :: T.Text
myTStr2 = "bar"
参照:
Textのビルダ
toLazyText :: Builder -> Data.Text.Lazy.Internal.Text
fromLazyText :: Data.Text.Lazy.Internal.Text -> Builder
Text のビルダを使えば、怠惰な Text 型を、文字列やリストのような非効率なものを中間物として経由することなく、効率的なモノイドの構成を作ることができます。
{-# LANGUAGE OverloadedStrings #-}
import Data.Monoid (mconcat, (<>))
import Data.Text.Lazy.Builder (Builder, toLazyText)
import Data.Text.Lazy.Builder.Int (decimal)
import qualified Data.Text.Lazy.IO as L
beer :: Int -> Builder
beer n = decimal n <> " bottles of beer on the wall.\n"
wall :: Builder
wall = mconcat $ fmap beer [1..1000]
main :: IO ()
main = L.putStrLn $ toLazyText wall
ByteString
ByteString は正格評価か遅延評価を用いる非ボックス文字の配列です。
pack :: String -> ByteString
unpack :: ByteString -> String
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
-- From pack
bstr1 :: S.ByteString
bstr1 = S.pack ("foo" :: String)
-- From overloaded string literal.
bstr2 :: S.ByteString
bstr2 = "bar"
参照:
printf
Haskell には、C のスタイルの可変個引数の printf
関数もあります。
import Data.Text
import Text.Printf
a :: Int
a = 3
b :: Double
b = 3.14159
c :: String
c = "haskell"
example :: String
example = printf "(%i, %f, %s)" a b c
-- "(3, 3.14159, haskell)"
多重定義されたリスト
データ構造のライブラリがリストから様々な構造を構成するために toList
や fromList
を公開しているのはよく見かけます。GHC 7.8 の時点では、型クラス IsList
を使って表層の言語でリストの構文をオーバーロードすることができます。
class IsList l where
type Item l
fromList :: [Item l] -> l
toList :: l -> [Item l]
instance IsList [a] where
type Item [a] = a
fromList = id
toList = id
λ: :type [1,2,3]
[1,2,3] :: (Num (Item l), IsList l) => l
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE TypeFamilies #-}
import qualified Data.Map as Map
import GHC.Exts (IsList(..))
instance (Ord k) => IsList (Map.Map k v) where
type Item (Map.Map k v) = (k,v)
fromList = Map.fromList
toList = Map.toList
example1 :: Map.Map String Int
example1 = [("a", 1), ("b", 2)]