読者です 読者をやめる 読者になる 読者になる

Just $ A sandbox

プログラミングと計算機科学とかわいさ

テキストフォーマットが欲しいという話

Haskellで時々Formatがしたくなる時があります。
しかしこれと言って便利そうなものが見当たらず、自作しました。

myuon/FuncFormat · GitHub

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 xg f xとして解釈できるのであれば別に_1やviewを使う必要もありません。
これによって、少なくともある程度抽象度を保ったままテキストのフォーマットができるようになっています(ると思い込んでいます)。

今のところはこれで特に不満もありませんが、もうすこしフォーマット以外に便利な使い方を思いついたら反映させるかもしれません。 という訳で、Haskellでも(たった100行そこらで(!))Formatは簡単にできるのだという話でした☆

(まぁどうでも良いことなんですが、あんまり何も考えずにこういうのを作って、作った後で同じ事をしているパッケージを見つけたのでちょっと悲しかったです…先に調べておくべきでした><)

参考

以下は私の今回作ったのとは一切関係のない、別のパッケージです

*1:Data.Text.Formatにしたかったのですが、すでにそういうパッケージがHackageに存在したので避けました