fugafuga.write

日々のログ

すごいH本 part67

bytestring

まず、遅延評価について

wikipedia より

評価しなければならない値が存在するとき、 実際の計算を値が必要になるまで行わないことをいう。 評価法が指示されているが実際の計算が行われていない中間状態の時 それをプロミス(英: promise)や、 計算の実体をさしてサンク(英: thunk)といい、 プロミスを強制(英: force)することで値が計算される。

リスト [1,2,3,4] を出力する時の評価について考える。

  1. リスト[1,2,3,4]1:2:3:4:[] の構文糖衣
  2. 出力時、先頭の 1 から評価される
  3. 残りの部分 2:3:4:[] はまだ評価されていないものとする
  4. この時、2:3:4:[] はプロミスであるといえる
  5. さらに、2:3:4:[] の計算を指して、サンク(thunk) という

上記のことから、 リストはプロミスであるといえる。 値が必要になった場合に、次の要素と後続のプロミスを渡してくれるようなプロミス。

しかし、単なる数のリストをサンクの列として処理するのは効率が悪い。 サイズの大きなファイルを操作する場合に、オーバーヘッドが問題となる。

そのため、Haskell には bytestring が用意されている。

bytestring はリストに似たデータ構造で、要素は1バイト(あるいは8bit)のサイズ固定。

正格 bytestring と 遅延 bytestring

正格 bytestring は Data.ByteString に定義されていて、遅延性が完全に排除されている。 サンクは一切ない。なので無限の 正格 bytestring のようなものは作ることができない。

遅延 bytestring は Data.ByteString.Lazy で定義されている。遅延評価はされるが、 リストほどではない。リストの場合、要素数と同じ位の数のサンクがあり、これが原因で 場合によっては遅くなる。遅延 bytestring の場合、64Kバイトのチャンク(chunk) という 塊にデータが格納され、遅延 bytestring が評価されたら、最初の 64Kバイトが評価される。 残りはリストと同じようにプロミスとなる。

メモリ使用量も抑えつつ、CPUのL2キャッシュにフィットするサイズになっている。

GHCiで実際に触ってみる。

*Main Lib> import qualified Data.ByteString.Lazy as B

<no location info>: error:
    Could not find module ‘Data.ByteString.Lazy’
    It is a member of the hidden package ‘bytestring-0.10.8.1@bytestring-0.10.8.1’.

また怒られが。

hidden package の対応は以前と同じようにしてみる。

my-project.cabal

executable my-project-exe
  hs-source-dirs:      app
  main-is:             Main.hs
  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
  build-depends:       base
                     , my-project
                     , random
                     , bytestring <= 追加
  default-language:    Haskell2010

再度

*Main Lib> import qualified Data.ByteString.Lazy as B
*Main Lib B> import qualified Data.ByteString as S
*Main Lib B S>

いけた。

B.pack という関数を見る

*Main Lib B S> :t B.pack
B.pack :: [GHC.Word.Word8] -> B.ByteString

Word8 のリストを受け取って、ByteString を返す。

Word8 型は 8ビット符号無し整数を表す。範囲は 0~255

実際に pack する

*Main Lib B S> B.pack [99,97,110]
"can"
*Main Lib B S> B.pack [90..120]
"Z[\\]^_`abcdefghijklmnopqrstuvwx"

unpack する

*Main Lib B S> let by = B.pack [98,111,114,116]
*Main Lib B S> by
"bort"
*Main Lib B S> B.unpack by
[98,111,114,116]

正格 bytestring と 遅延 bytestring を相互変換することも可能

toChunksは、遅延 bytestring を受け取って、正格 bytestring のリストに変換する

*Main Lib B S> B.toChunks $ B.pack [1..255]
["\SOH\STX\ETX\EOT\ENQ\ACK\a\b\t\n\v\f\r\SO\SI\DLE\DC1\DC2\DC3\DC4\NAK\SYN\ETB\CAN\EM\SUB\ESC\FS\GS\RS\US ","!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`","abcdefghijklmnopqrstuvwxyz{|}~\DEL\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224","\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255"]

fromChunksは、正格 bytestring のリストを受け取って、遅延 bytestring に変換する

*Main Lib B S> B.fromChunks [S.pack [40,41,42], S.pack[43,44,45], S.pack[46,47,48]]
"()*+,-./0"

bytestring 版の :cons

*Main Lib B S> B.cons 88 $ B.pack [1,2,3,4,5]
"X\SOH\STX\ETX\EOT\ENQ"

bytestring モジュールの関数は以下にに全部載っている

http://hackage.haskell.org/package/bytestring

bytestring モジュールには System.IO モジュールが提供する関数と同様の動作を する関数もある。String の代わりに ByteString を受け取る。

*Main Lib B S> :t readFile
readFile :: FilePath -> IO String
*Main Lib B S> :t B.readFile
B.readFile :: FilePath -> IO B.ByteString
*Main Lib B S> :t S.readFile
S.readFile :: FilePath -> IO S.ByteString

所感

遅延評価がネックになってくる部分を補うための方法が用意されていてよい。

promise と thunk はまだイメージがふんわりしているので、 進めながら理解していきたいところ。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

  • 作者: MiranLipovaca
  • 出版社/メーカー: オーム社
  • 発売日: 2017/07/14
  • メディア: Kindle版
  • 購入: 4人 クリック: 9回
  • この商品を含むブログを見る