Haskell でバグの出にくいプログラミング (3)

前々回前回と、副作用がバグの原因の一つであるということから、Haskell で副作用を分離して、バグの少ないプログラミングをしましょうという記事を書きました。

これまで、IO 制御による副作用について書いたのですが、今回から 2, 3 回ほど、別の副作用である変数への代入について書こうと思います。

変数代入はバグの温床

プログラミングに於ける副作用 (wikipedia) とは、コンピュータの論理的な状態の変化により、後の処理に影響を与えるものです。 論理的な状態の変化と聞いて、多くのプログラマが真っ先に思い付くであろうものが、変数への代入です。 変数はプログラマの世界では日常的に使われすぎて、そこに潜むバグについてあまり深く考えたことのない人もいるかもしれません。 しかし現実には、予期せぬ変数の代入、代入忘れによるバグはよく発生し、システムが大きくなるにつれて、頻繁に表れるようになる気がします (おそらく、変数が多くなるため)。

これらのバグを減らすために、カプセル化という概念が生み出され使われています。 私がバグが出にくいと推奨する Haskell では、どのような対策を講じているのでしょうか。

参照透過性

そのために、まずは Haskell の参照透過性 (wikipedia) について述べる必要があります。 Haskell では、関数は引数が同じであれば、「いつ」「どこで」呼んでも同じ値を返すという、参照透過性が満たされています。 例えば、以下の Haskell のプログラムを見てください。

x = 3
main = print x

x は定数関数とみなせ、その引数は 0 個です。 よって、参照透過性から、x を「いつ」「どこで」呼んでも、同じ値 (この場合は 3) が返ってきます。 プログラムの途中で、x に 4 を代入することができたとすると、代入後に x を呼び出した時、引数が同じ (引数が 0 個なので) にも関わらず、値が変わってしまいます。 このように、動的に値を変更する変数の代入のような操作は、参照透過性を満たさないため、Haskell ではできません。

ただし、スコープが別であれば、同名の外側の関数が隠蔽されます。

x = 3
main = print x
where
x = 100
この場合、100 が出力されますが、グローバルスコープの x へ 100 を代入しているわけでは無く、ローカルスコープで新たに x と名付けられた値を定義しただけなので、外側の x にはまったく影響がなく、参照透過性は満たされています。

つまり、そもそも変数という概念が Haskell には存在しないのです。

純粋関数型

参照透過性を満たしている言語は、純粋関数型言語と呼ばれ、Haskell もその中の一つです。 純粋でない関数型言語も純粋関数型を目指して設計されていると思われるのですが、やはり変数の有用性を捨てきれず、代入を許す構文または関数が用意されています。

しかし、Haskell は参照透過性を満たしながら、変数を扱うという一見不可能にも思えることができるのです。 厳密には、変数と同じような機構を純粋関数的に模倣することができるのです。 というわけで、次回はそのような性質を持つ、State モナドについて書こうと思います。

担当:齋藤 (private が好きです。でも、const の方がもっと好きです。)