エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Haskellのちから

必要に迫られて、タブを空白に展開する機能が必要になり、自分でコードを書く。行に分割すれば、改行を考えなくてよいので、単純に一行分の文字列を展開するものです。

考えた手順はこんな感じ。

  1. 文字列から一文字ずつ読み込み、タブ文字でなかったらそのまま。
  2. タブ文字だったら、必要な空白文字列に置き換える。
  3. 必要な空白文字列の長さは、タブの桁数から1までの間のいずれか。
  4. 空白文字列の長さは、一文字読み込むたびに1ずつ減らし、0になる場合にはタブの桁数にする。
  5. タブ文字を置き換えたときは、空白文字の長さはタブの桁数にする。
  6. 読み込む文字がなくなったらおしまい。
我ながら、あまり文章がよくないですが、要はタブを空白文字に展開するということです。

この考えをもとに、C++で書いたのが次のコード。わかりやすさ優先、効率が悪いところもあえてそのまま。それ以前のデキの善し悪しはちょっと脇において(ご覧になっている方がいらしたら、ツッコミ歓迎です)。

#include <string>
std::string tabToSpace(int tabSize, const std::string& s)
{
    std::string result;
    int         spacingSize = tabSize;
    for(std::string::const_iterator i = s.begin(); i != s.end(); ++i)
    {
        if(*i == '?t')
        {
            result += std::string(spacingSize, ' ');
            spacingSize = tabSize;
        }
        else
        {
            result += *i;
            --spacingSize;
            if(spacingSize == 0)
            {
                spacingSize = tabSize;
            }
        }
    }
    return result;
}

勉強がてら。これをHaskellでも書いてみました。

tabToSpace tabSize s = tabToSpace' tabSize s
  where tabToSpace' _ ""        = ""
        tabToSpace' 0 s         = tabToSpace' tabSize s
        tabToSpace' i ('?t':cs) = (take i $ repeat ' ') ++ (tabToSpace' tabSize cs)
        tabToSpace' i (c:cs)    = c:(tabToSpace' (i-1) cs)


書き上げてしばし固まりました。なにこの簡潔さ、単純さ、短さ。きっと何かを忘れているに違いないと確かめたんですが…上のC++のコードと同じ内容のようです。定義を当てはめてみると、どちらのコードにも1から6が入ってます。同じ内容のようです。

Haskell向きのお題だったとはいえ、この結果に関数型言語の力を見た気がします。「仕事もHaskellで書けたら…」と妄想。3秒で醒めました。さすがにそれは今は無理。