Haskell でも heredoc がしたい

string gap の紹介

Posted by mizunashi_mana on April 17, 2019Tags: heredoc, CPP

多くの言語では, here document (heredoc) という言語機能が搭載されています.これは,複数行の文字列をコード中に文字列リテラルとして埋め込める機能です.今日は heredoc ほど使い勝手がよくないものの,長い文字列を埋め込める, Haskell 標準の string gap という機能を紹介したいと思います.

Link to
here
string gap

bash では,複数行の文字列を,次の記法で埋め込むことができます:

echo "$(cat <<EOS
some text
is multilined
EOS
)"

これは,

some text
is multilined

という文字列が出力されます.多くの言語では似たような構文で heredoc が採用されていて,特殊な記号の後に終端記号を書いて,その後の終端記号までを文字列リテラルとして扱われます. Haskell では残念ながらこのような機能は搭載されていませんが,代わりに次の記法が提供されています:

main :: IO ()
main = putStrLn "\
  \some text\n\
  \is multilined\
\"

この実行結果は,前の bash スクリプトの結果と同じになります. heredoc より色々ごちゃごちゃしてますが,複数行の文字列リテラルを書けます.この機能は, Haskell の複数行文字列リテラルまたは Haskell 標準では gap と呼ばれています 1.記法はかなり単純で,文字列中のバックスラッシュ \ で囲まれた空白が無視されるだけです.改行も空白に含まれます.なので,上のプログラムは以下のプログラムと同じです:

main :: IO ()
main = putStrLn "some text\nis multilined"

なお, gap を使わないで複数行の文字列リテラルを書くことはできません.また, gap は空白を全て無視するため,改行を含まない長い文字列を複数行に渡って埋め込むのにも使えます:

main :: IO ()
main = putStrLn "This is very very very \
  \long long long long long long long long text."

なお, gapHaskell 標準でレイアウトルールの処理から除外されているため2,インデントを考慮する必要はありません:

main :: IO ()
main = do
  putStrLn "one line"
  putStrLn "\
\multiline\n\
\text\
\"

Link to
here
CPP 下での注意事項

ただ, GHCCPP 拡張を使用する際注意が必要です. CPP では,バックスラッシュで終わる行は,バックスラッシュを除いて次の行と繋げる処理が行われます3.この処理のため, gap を使用した以下のコードは,

{-# LANGUAGE CPP #-}

main :: IO ()
main = putStrLn "This is very very very \
  \long long long long long long long long text."

cpp により次のように変換されてしまいます:

main :: IO ()
main = putStrLn "This is very very very   \long long long long long long long long text."

このため,結果的にコンパイルエラーになってしまいます.このため, CPP を使う際は, gap を使わず CPP の機能を使う必要があります.例えば,上記のプログラムは,

{-# LANGUAGE CPP #-}

main :: IO ()
main = putStrLn "This is very very very \
\ \long long long long long long long long text."

と書くと gap をそのまま使った時のプログラムと同じになります.一番最初の \CPP のためのもの,次の 2 つは gap になります.

Link to
here
まとめ

string gap は,昔から Haskell 標準で付いている機能なので,ぜひ使ってみてください.

ただ, heredoc より使い勝手は良くないです.変数展開やもう少し見栄えの良い heredoc が欲しい場合は, here パッケージShakespeare などの TemplateHaskell を使ったテンプレートエンジンの使用を検討してみるといいかもしれませんね.

では,今日はこれでノシ


  1. Haskell20102.6の最後の方で紹介されています.↩︎

  2. Haskell201010.3 で触れられています.↩︎

  3. CPP の仕様の 1.2 で触れられています.↩︎