ML で unit 型を取る関数の arity

(この記事は2006/1/11に書かれたものです。移動してきた関係でコメントも投稿日時が正しくありません)

超特急: 一時間でわかるML超入門 を読んでふと。

 # let f () = ();;
 val f : unit -> unit = <fun>
 # let f (x:unit) = x;;
 val f : unit -> unit = <fun>
 # let f (x:unit) (y:unit) = (x, y) ;;
 val f : unit -> unit -> unit * unit = <fun>

当たり前のことですが、unit 型を引数として取る関数って () だけを引数として取るだけで、arity が 0 ってわけじゃないんですよね。いや、arity が 0 の関数ってもはや関数という枠組みから外れているわけですが。

ただなんとなく、f を評価するときには引数が必要ないと分かっているのに、わざわざ () と書かせるのが汚いなぁ、とふと思っただけなんですけど……。

毎回評価する定数、という存在を考えてみる、と思考実験を試みようかと思いましたが、不毛なのでやめました。そもそも、unit 型を引数として取る関数が必要ということが理論的にはおかしいので(純粋な関数型言語であれば定数で置き換えられるはずなので)、枠組みを単純化するための妥協の産物としてあきらめるしかないんでしょうね。syntax sugar でごまかすことは可能かもしれませんけど。

追記

ベッドの中で考えてしまい、眠れなくなりました……。

unit 型を引数として取る関数が純粋な関数型言語では必要ない、というのは言いすぎでした。環境を引き継いだ場合は返す値が毎回異なる unit -> 'a な関数が存在しますね。

 # let f x = let g () = x + 1 in g () ;;
 val f : int -> int = <fun>
 # f 1 ;;
 - : int = 2
 # f 2 ;;
 - : int = 3

そう考えると、話は遅延評価(しつつ、値をキャッシュしない)機構を言語に組み込むかどうかという話な気がしてきました……。たとえば、↓のような(仮想の) let macro 構文。

 # let macro dice = (Random.int 6) + 1;;
 # dice;;
 - : int = 5
 # dice;;
 - : int = 2

そんなものを考えるくらいなら unit 型を導入したほうがよっぽどすっきり、というのにはとても同意します(^^;言語デザインの観点から言えば、定数か定数でないのかは見た目で判断できるべきですしね。