パッケージnowdocの紹介
はじめに
Haskellで「式展開しないで、できるだけ、もとの文字列のままでコード内に置くことのできるヒアドキュメント」であるナウドキュメントを、実現するのがパッケージnowdocだ。QuasiQuoterを使って数行で実装できるので、手作りするのもいいけれど、このパッケージを使えば、よりお手軽だ。
ちなみに
「まじめな用途」で使いやすい「高機能なヒアドキュメント」を使いたい場合は、こちらを参照。
パッケージnowdocは、「コード内にアスキーアートを置きたい」など、「お遊び」的な用途で使いやすい。
ナウドキュメントとは
コード内に「そのまま」文字列を置きたいとき、ヒアドキュメントの機能を使う。たとえばRubyだと、つぎのようになる。
puts <<EOS
Hello, world!
Good-bye, world!
EOS
これは、つぎのコードとおなじだ。
puts "Hello, world!\nGood-bye, world!"
複数行にわたる文字列を定義したいときなどに便利に使える。これがヒアドキュメントだ。Rubyの文字列定義では式展開が使える。
x = 123
puts "Hello, world!\n#{x}"
ヒアドキュメントでも、おなじことができる。
x = 123
puts <<EOS
Hello, world!
#{x}
EOS
もし、`#{x}'そのものを「そのまま」書きたいときは、どうするか。EOSをシングルクォート(')でかこめばいい。
x = 123
puts <<'EOS'
Hello, world!
#{x}
EOS
このようにすれば、`#{x}'は式展開されなくなり、そのままの文字列になる。このような「式展開しないヒアドキュメント」をPHPerはナウドキュメントと呼ぶ(らしい)。
仕様
- `[nowdoc|'ではじまり`|]'でおわる
- コード中に書かれた文字列の、ほぼそのままの文字列になる
- 先頭に改行がある場合、ひとつだけ除去される
- `|'ではじまり、n個のスペースのあとに`]'でおわる文字列は、`|'ではじまり、n-1個のスペースのあとに`]'であわる文字列に変換される
導入のしかた
ここではStackを使った例を挙げる。まずは、stack newをする。
% stack new try-nowdoc
% cd try-nowdoc
パッケージnowdocは、まだLTSに含まれていないので、resolverをnightlyにする。
% vim stack.yaml
...
resolver: nightly-2018-11-30
...
package.yamlを編集して、nowdocを使うようにする。
% vim package.yaml
...
dependencies:
- base >= 4.7 && 5
- nowdoc
...
この段階で一度ビルドしてみよう。
% stack build
...
% stack exec try-nowdoc-exe
sumFunc
Hello, nowdoc!
まずは、あいさつからだ。app/Main.hsを編集する。
{-# LANGUAGE QuasiQuotes #-}
import Text.Nowdoc
main :: IO ()
main = putStrLn [nowdoc|Hello, nowdoc!|]
ビルドして実行してみよう。
% stack build
% stack exec try-nowdoc-exe
Hello, nowdoc!
こういうとき、いいね
式展開しないことの良さは(そのあたりのコードを書くのが、めんどくさいというパッケージ作者の都合以外では)、メタ的な内容を書くときに感じられる。
explanation :: String
explanation = [nowdoc|
式展開をするには
* #{x}のようにしたり
* ${x}のようにしたりする言語が多いかな
* \{x}とする場合もあるかな
* %{x}なんてのもあるかもしれない
パッケージnowdocを使う場合には
* 文字列は`[nowdoc|'ではじめて、`| ]'でおわらせる
* `| ]'自体を書きたいときには`| ]'のようにする
+ `|'と`]'のあいだにスペースを置けばいい
|]
こんな感じの「説明」を書くときに便利。まあ、こういう(すごく限られた用途の)とき以外には式展開するヒアドキュメントのほうが便利ではあるのだけど。試してみよう。
% stack ghci
> putStr explanation
...(省略)...
アスキーアート
コードのなかにアスキーアートを、そのまま置きたいときなんかにも使いやすい。
lena :: String
lena = [nowdoc|
==\...........\$$=\\\\\\\\\\\\\\\\\... ...... .\==$$$$$$$$$$$$=\..\\==\
==\............$$$==\\\\\\\\\\\\... ..... ..\==$$$$$$$$$$=\\\\\\===\ .
==\............\$$==\\\\\\\\\.... . ...\=$$$=====$$$$\...\\===. .\
==\..........\\.\$$\\\\\\\\.. . .. ..\=$$======$$$$$=\...\=\ .\\
==\.........\\\.\$=\\\\\\.. .. .\====$======$$$$$$$=. .=. \\=
==\.........\\\\\\=\\\\. . ..\==$==$======$$$$$$$$$\ \. \\==
==\.........\\\\\..\\\. . .\===$$=\\\=====$$$$$$$$\ \\ .\\==
==\.........\\\\\..\... . .\==$===\\\\..\\===$$$==\.. .\ .\\===
==\.........====\. .... .\=$$$\. \. .\\==$$\. . .=. \\====
==\.........===. ... .\=$$=\\....\=$\\\\\=$=...\. .=.. .\=====
==\....\\\\\\. ... ..\\ .==$=\\\==\\\====\\\\=$$=\\\.. =\ \\=====
==\...\\\\\. .... ..... .=$=..\\\==========\\\=$$==\\\. \\ .\======
==\...\\\......\. \. .=$\ .\\\=========\\\\=$$===\\. \\ .\=======
==\..\\\.... ..\. ...\. .\=\ .\\\\========\\\\\$$===\\. .=. \\=======
==\...\\... ....\. ... .\==. ..\\\\\======\\\\\\=$==\\. .=\ .\========
==\..... .......\\ . ....\=\ ...\\\\\=======\\\\\===\\. \\ \=========
==\...\\ .. ...\\.. .\==\\. ...\\\\\\\========$$$==\. . \\\\=========
==\...\. . ........\==\\. ..\\\\\\\\\\\\\\\==\\\\ . .\\==========
==\...\.. .\.....\\==\. ...\\\\\\\\\\\\\===\\. . .============
==\..\. . \\\..\.\\=\. ...\\\\\\\\\\\\\=\. .. .===========\
==\...... \..\\.\\\\ ..\\\\\=======. .... .===========\
==\... \...\\\\\. ..\\\\=======. .... .=\\\\\\\\=\\
===\.. . .\..\..\.\\. .\\\\\\\\=====$$==\. .... .=\\\\\\\\\\\
==\\. .. .. ......\\\... .\\\\\\\\\\===$$$$$$$\. . . .=\\\\\\\\...
|]
知る人ぞ知るレナさんをアスキーアート化したものを、コード内に直接、書きこんでみた。試してみよう。
> :reload
> putStr lena
...(省略)...
おまけのQuasiQuoter
(コンパイル時に)ファイルを読み込んで、それを文字列としてコード内に置くことのできるQuasiQuoterが2種類、用意されている。
txtfile
abcdefg
012345
あいうえお
text :: String
text = [txtfile|some.txt|]
コンパイルして試してみる。
> :reload
> putStr text
abcdefg
012345
あいうえお
txtfileはシステムのデフォルトの文字コードにしたがって、ファイルを文字列に変換する。
binfile
binfileはファイルをバイナリ列として解釈する。
bin :: String
bin = [binfile|some.txt|]
試してみる。
> :reload
> bin
"abcdef\n0123456\n\227\129\130...\129\138\n"
binfileはOverloadedString拡張とパッケージbytestringとの相性がいい。まずは、適当なバイナリファイルを作成する。
head -c 1k /dev/urandom > some.bin
/dev/urandomのない処理系(Windowsとか?)のユーザは、小さめのバイナリファイルを適当に用意しよう(アイコン画像とか?)。
src/UseBinfile.hsを作成する。
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module UseBinfile where
import Text.Nowdoc
import qualified Data.ByteString as BS
someBinary :: BS.ByteString
someBinary = [binfile|some.bin|]
package.yamlを編集する。
% vim package.yaml
...
dependencies:
- base >= 4.7 && < 5
- nowdoc
- bytestring
...
試してみる。
> :reload
> someBinary
"\131\DLE9W\146...\178\215\202\US/\162"
まとめ
式展開などのない単純なヒアドキュメントであるナウドキュメントの、Haskellでの実装であるパッケージnowdocを紹介した。できるだけ、もとの文字列に近い文字列として表現できるようになっている。QuasiQuotesを使えば数行で実装できるので、あえてこのパッケージを使うまでもないが、これを使えば、すこしだけ楽ができる。
また、おまけとしてファイルの内容を(コンパイル時に)文字列としてコードに取り込むのに使えるQusiQuoterも紹介した。
夢が広がる
binfileを使うと、自己解凍ファイルとか作れそう。うまくいくようなら記事にしたい。