テキストフォーマットが欲しいという話
Haskellで時々Formatがしたくなる時があります。
しかしこれと言って便利そうなものが見当たらず、自作しました。
import Text.FuncFormat
とすれば使えるようになります*1。
ほんの少しだけTemplateHaskellの闇魔術を利用しているので、準クォートによって分かりやすく(?)テキストを整形することが可能です。
以下はそのサンプルです。
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} import Text.FuncFormat import Data.Text import Control.Lens -- [format| {_1} |] f (a,b) == pack $ show $ f _1 (a,b) main = putStrLn $ unpack $ [format| {_1}, {_2}!|] view view ("hello", "world") -- output: hello, world!
やっていることは単純で、[format|
と|]
で囲まれた部分を文字列として受け取って、Parsecを用いてパースして函数を適用させて元の文字列に埋め込んでいるだけです(詳しい使い方はgithubのREADMEでも読んで下さい)。
ちょっとだけ裏話
Control.Lensを使ってタプルから値を取得する、というアイデアは最初からあったのですが、実は[format| {_1} {_2}|] view ("hello",100)
などとして埋め込もうとすると、「viewの型が文字列なのか数値なのか分からない!」とコンパイラに怒られます。
上の例だとview _1 ("hello",100)
とview _2 ("hello",100)
を同じ文脈で評価していますが、前者の場合viewは文字列を返し、後者の場合は数値を返すことになるので同じ函数が場所によって異なる型として推論されてしまってコンパイラに怒られるというわけです。
という事情があって、仕方なく[format| {_1} {_2}|] view view ("hello!", 100)
とすることで、{}の中の函数と[format|...|]
に続く函数がそれぞれ対応するということにしてコードを組みました。
ちなみにですが、やたらとviewにこだわっていたのは、
[format| {_1}!|] (\x (y,z) -> set x y z) ("hello", (10,100))
のようにしてview(値を取り出す)以外にも、set(値を更新/書き換えて反映させる)などが出来るようにするためです。
もちろん、[format| {f} |] g x
がg f x
として解釈できるのであれば別に_1やviewを使う必要もありません。
これによって、少なくともある程度抽象度を保ったままテキストのフォーマットができるようになっています(ると思い込んでいます)。
今のところはこれで特に不満もありませんが、もうすこしフォーマット以外に便利な使い方を思いついたら反映させるかもしれません。 という訳で、Haskellでも(たった100行そこらで(!))Formatは簡単にできるのだという話でした☆
(まぁどうでも良いことなんですが、あんまり何も考えずにこういうのを作って、作った後で同じ事をしているパッケージを見つけたのでちょっと悲しかったです…先に調べておくべきでした><)
参考
- TextFormat - github 今回私が作ったもの
以下は私の今回作ったのとは一切関係のない、別のパッケージです
*1:Data.Text.Format
にしたかったのですが、すでにそういうパッケージがHackageに存在したので避けました