Haskell が教えてくれたこと

2020年1月15日

Haskellハスケル というプログラミング言語をご存知ですか? 関数型プログラミングという、近ごろ主流となりつつあるパラダイムの先駆けともいえる言語です。

日本では2006年頃にちょっとした Haskell ブームがありましたが、その潔癖な言語仕様ゆえに万人には受け入れられず、今のところ既存の言語を置き換えるまでには至っていません。私も少し勉強したことがありますが、あまりに難解で、結局マスターすることはできませんでした。

しかし Haskell を勉強したことは、私の C++ の書き方に良い影響を与えてくれました。今回はそれについての話をしますが、Haskell のコードは一切登場しませんのでご安心を。

変数の代入をしない

Haskell という言語を最も際立たせている特徴は、何といっても「変数の代入ができない」という仕様です。これには誰もが驚きます。

さすがに C++ でそこまで極端なプログラムは書けませんが、Haskell の思想を学ぶにつれ、気づかされます。「今まで代入でやろうとしていたことは、実は代入でなくてもよかったのだ」と。

代入と初期化は違う

「代入を使わない」とは、言い換えると「初期化を使う」ということです。ここで、代入と初期化の違いを明確にしておきましょう。C++ では、次のコードで行っていることは代入と呼ばれます。

int x;
x = 1;

次のコードは初期化です。

int x = 1;

ほとんど違いがないように見えますが、ここで x がクラスインスタンスだった場合、前者は代入演算子、後者はコピーコンストラクタで処理されることからも、これらが明らかに区別されていることがわかります。

もう一つ、違いを示しておきます。次のコンストラクタで行っていることは、メンバ変数の代入です。

class Real
{
private: double m_value;
public: Real(double x) { m_value = x; }
};

次のコードは、メンバ変数の初期化です。

class Real
{
private: double m_value;
public: Real(double x) : m_value(x) { }
};

ここでも同様に m_value がクラスインスタンスなら、前者は代入演算子、後者はコピーコンストラクタに処理が委ねられます。

また、次のような操作は、すべて代入の部類に入ります。

++x;
x += 2;
x &= 4;
x <<= 8;

初期化に依存する

代入を多用するプログラムがなぜいけないかというと、読み手の脳に負担をかけるからです。私は代入だらけのプログラムを読んでいると、ボールの入っているコップを当てる、あの手品を思い出します。変数の動きを注意深く目で追い続けないといけないからです。

例を挙げましょう。次の関数がやろうとしていることが、すぐに読み取れるでしょうか?

int func(int n)
{
  bool flag;
  if (n < 0)
  {
    n = -n;
    flag = true;
  }
  else
  {
    flag = false;
  }
  if (n < LOWER)
  {
    n = LOWER;
  }
  else if (n > UPPER)
  {
    n = UPPER;
  }
  if (flag)
  {
    n = -n;
  }
  return n;
}

シャッフルされるコップを見つめているような気分になりますね。このコードをまず、単純に代入を避けて初期化に依存するように変えてみます。

int func(int n)
{
  bool flag = (n < 0);

  int n1 = flag ? -n : n;

  int n2 = (n1 < LOWER) ? LOWER
         : (n1 > UPPER) ? UPPER
                        : n1;

  int n3 = flag ? -n2 : n2;

  return n3;
}

プログラムの流れが見えてきましたね。

変数を消去する

それだけではありません。代入を使わないプログラムが素晴らしいのは、心置きなくリファクタリングができるという点です。上のコードをつぶさに見ると、なんと「変数」さえも消去できることがわかります。

static int clamp(int n, int lower, int upper)
{
  return (n < lower) ? lower
       : (n > upper) ? upper
                     : n;
}

int func(int n)
{
  return (n < 0) ? -clamp(-n, LOWER, UPPER)
                 :  clamp( n, LOWER, UPPER);
}

このコードには、数式に似た美しさがありませんか? これが Haskell 的な C++ のコードの一例です。初心者の方には一度、プログラミングを「代入を減らすパズル」だと思って取り組んでみることをお勧めします。あなたのプログラムの品質が向上することは間違いありません。