【注意】ディスりたいわけではありません
Unit型の何が偉大なのか説明してみる を読んで,Unit と void の違いを考えるとっかかりとしてすごくいい記事だと思うし,簡単に説明しようと思うとそうなるのもわかるんだけど,個人的にしっくりこないところがあったので,何がしっくりきていないのか確認しながら void と Unit の違い,Unit の必要性を考えようと思いました.
ちなみに以下はほぼ脳内会議の議事録なので,記事中の言葉遣いは荒めです.
P.S. はじめてポエムタグつけてみました.
void と Unit の違い
これはもう参考記事でも言及してるし,しっくりきてるから一言で, 違いは値を持つか持たないかの一点.
- void : 値がないことを示す型.本当に値がない.
- Unit : 値がないことを示す型.値がないという唯一の値を持つ.
Unit の必要性
要は
関数の汎用性(抽象度?)を考えたときに,すべての関数は何かしら値を返すもの,あるいはすべて値など返さないもの,のどちらかに決まってないとバツが悪いので,どっちかに統一しましょうと.
んで,そりゃすべての関数が値を返さないってのは無理があるから,すべて関数は何かしらの値を返すことにしましょうと.
そもそも
void型の関数が存在して問題になるのってどんなとき?ってのが,関数合成の例では自分の中でしっくりこなかった.
参考記事でコメントももらったけど,根性と性格の曲がりきった自分は
- 関数合成の途中で Unit 挟むって,その関数から先はもはや別のプログラムじゃん
- 確かにデータベース操作とか順番に依存して,かつプログラムを分割できないケースもあるとは思うけど,Haskellにだって
do
あるし,それをわざわざ関数合成できるようにして,一つの関数でもってプログラムを構成することに意味はあるのか
などと考えてしまい,いまいち納得しきれずにいた.(何度も言いますが,disってるわけではない)
一方,抽象的な関数の代表例として高階関数を考えたとき,なんだかしっくりきた.
例えば map
関数 だ.
こいつは何かしら値の入ったリストなり配列なりを受け取って,渡された関数を適用した結果の値をリストなり配列に入れて返すわけだ.
プログラマーはときにリストなり配列なりの中身を確認したくなる生き物である.
このとき,どうせ全要素回してくれるならと,map
に中身を出力するだけの関数(void型)を渡そうとする.(C言語の printf
は int型じゃんというツッコミは華麗にスルーさせていただく)
するとどうだ,map
は中身を出力するだけに飽き足らず,void型のリストを返そうとする.
当然だ,それが本来の仕事なのだから.
値のない型の値をリストに突っ込んで返そうとするのだ.そりゃエラーになる.
この用途に map
は適さないのだ.
ちなみに,Rubyなんかにはこういう用途向けの each
という関数が存在する.(もちろんRubyではこれを map
で代用できるが,それは今どうでもいい)
でも待ってくれ,ほとんど同じ内容の関数を void型向けとそれ以外向けとで2つも用意するなんてクールじゃない.(C++では型に合わせていくらでも特殊化するが,まぁ呼び出し側には無関係だから...)
本来,map
で each
みたいなことができるべきだし,それが抽象度の高い関数というものだ.
では,どうするべきか
→ 何か値を返せばいいじゃない
→ でもテキトーな値返すのはなぁ
→ 何も値がないことを示す値があればいいんじゃね?
お待たせしました Unit の登場である.
こう考えたら,関数は全て値を返すものとして統一していた方が都合がいいし,特別な型として Unit あった方がいいわ.
結論
以上のような脳内会議を経て,参考記事で最初に書いてた部分が結論として納得のいくものになりました.
同じように引数を取るものに対して、値を返すものと返さないものの二種類が存在してしまっていたわけです。これだと困ることがあります。
余談ですが,これを考えていたら Lisp の空リストって結構秀逸というか,もう Unit そのものじゃないかと思えてきました.
もしかして 空リスト=最小単位=unit なのかなとも思ったくらい.(要調査)
こんなことを考える機会をくれた参考記事と,その著者さんに感謝.