Unit
型以前 〜C言語とかPascalとか〜
みなさんは数学の時間に関数をどのようなものとして習ったでしょうか?
ある値$x$に対して,ただ1つの値$y$が対応するような関係$y = f(x)$
という感じだったのではないかと思います。
さて、ここで重要なのは「ただ一つの値 $y$ が対応する」というところです。これはプログラミングに置き換えて言えば、何らかの値が返ってくる、ということになります。
次のC言語のコードを見て下さい。
int foo(int x) {
return x * 2;
}
void bar(int x) {
printf("%d\n", x * 2);
}
このfoo
とbar
は、どちらもC言語の言葉では関数と言います。ですが、この二つには明確な違いがありますね。
foo
はint
型の値を返すのに対して、bar
は値を返しません。なので、foo
は数学的にも関数と呼べるかもしれませんが、bar
は全く呼べないでしょう。
PascalやFortranはより明確に分かれていて、値を返すものは関数 $function$、値を返さないものは手続き $procedure$と呼んだりします。
ともかく、同じように引数を取るものに対して、値を返すものと返さないものの二種類が存在してしまっていたわけです。これだと困ることがあります。例えば、関数同士であれば合成できますが、そうでないときは難しいでしょう。
Unit
型の登場 〜MLやHaskell、Scala〜
これはあんまりだなぁ、と思ったのかは分かりませんが、その後の言語ではUnit
型というものが登場してきます。
Unit
型というのは値が無いことを表す型で、値をただ一つのみ持つのが特徴です。
これによって上に挙げたbar
ような手続きも、foo
と同様に関数として書くことができるようになりました。例えばScalaなら、こんな風に。
// int foo(int x) { ... に対応
def foo(x: Int): Int = x * 2
// int bar(int x) { ... に対応
def bar(x: Int): Unit = println(x * 2)
// 合成できる!
((foo _) andThen bar)(100)
//=> 400
言ってしまえばこれだけなのですけど、それまで手続きと関数に分離していたものが、一つに統合された、ということは結構なことなんじゃないかと思います。
あとがき
void
とUnit
の違いって何なの? どうしてvoid
じゃダメなの? と訊かれたときに答えられるようになる程度の記事だと思います。それでも、こんな些細なことに気付いて感動できるのなら、それはきっと素晴しいことでしょう。こういうことを考えるのが面白いと思う人は、次はいわゆるnull
とUnit
の違いについて考えてみるといいかもしれません。
ちなみに、この引数の数と返り値の数とは結構深淵な問題で、例えば、多くのプログラミング言語では引数は複数を取るのに対して、どうして返り値は一つなのでしょうか? この疑問には恐らく、「多値 プログラミング」とかでググれば、答えてくれるページがあるかと思います。反対に、関数型言語ではタプルや関数を返す関数によって、一つの引数に対して一つの返り値が対応するように設計されていたりするわけです。ここでもUnit
は役に立っていますね。
最後に、短かい記事でしたが目を通していただきありがとうございました。