DEV Community

SUZUKI Tetsuya
SUZUKI Tetsuya

Posted on

OCaml のエントリポイント

※この記事は 2014年11月04日 に Qiita に投稿したものです。


以前「ゼロから始める OCaml」という記事を書いたのですが、その中で “Hello, world!” と出力するコードを例に挙げました。このコードは対話環境 (ocaml コマンド) に入力しても、ファイルに保存してコンパイルしても、どちらでも無事に実行されます。

let () = Printf.printf "Hello, world!\n"

let () =let _ = としてもOKです。いずれにせよ、 let () = から始まるこの式はエントリポイントなわけですね。

しかし OCaml のコードを書いていると気が付きますが、 let () =let _ = も単なるパターンマッチ、または単なる変数束縛です。なぜ単なる let 式がエントリポイントになるんでしょうか。

他の言語、特に OCaml と同系統の言語ではエントリポイントがどうなっているのか、ちょっと見てみましょう。

まあ、じゃあ... Haskell で。

main = putStrLn "Hello, World!"

Haskell では main 関数がエントリポイントとして実行されます。わかりやすいですね。

F# ではこうです。 EntryPoint 属性を指定した関数がエントリポイントとなります。

[<EntryPoint>]
let main args =
  printfn "Arguments passed to function : %A" args
  0

どちらの言語でもエントリポイントには専用の条件があります。 なぜ OCaml では単なる let がエントリポイントになるのか - もしトップレベルの let () =let _ = が特別な扱いを受けるのであれば、私もマニュアルやら技術書やらで目にしていると思うのですが、どうもそんな覚えがありません。お前ろくに技術書なんか読まねえだろって? うるせーよ。

とすると...「なぜ単なる let () =let _ = がエントリポイントになるのか?」も気になりますが、「なぜ他の let による関数定義がエントリポイントにならない」んでしょうね? そんなの当然だろ何言ってんの、と言われそうです。

うーん、なんで当然なのかと言ったら、関数って呼ばれなきゃ評価されないからですよね。じゃあいつ呼ばれるのかと言ったら、そりゃ呼ばれたとき...などと繰り返しているとバカみたいですけど、正格評価とか遅延評価とか私も耳にしたことがあります。 Haskell は遅延評価ってのが有名ですね。

エントリポイントの話が宙ぶらりんのままなんで、関数呼び出しがどっから始まるのかという問題はさておき、関数が呼ばれる可能性のある部分的な状況を考えてみます。ややこしいですけど、要は関数に渡す引数のことです。例えば次のような式がある場合、

f x y z

大抵の言語だと、引数 x y z の式は左から順に評価されます。 Haskell ではわかりません。いや本当に。どっかで必要になったら初めて計算されるのが遅延評価なんで、この式だけではわかりません。関数 f 内で x y z の値が必要になったら評価されます。

さて OCaml ではどうなのかと言うと、プログラミング in OCaml にこんな一文を見つけました。 (「8.3 制御構造」、 164 ページ)

現在の実装では、後ろの引数から順に評価が行われるのですが、 OCaml の言語仕様としては引数の評価順序は未定義なので、...

えっ、今なんと? OCaml の言語仕様としては引数の評価順序は未定義? な、なんだってー! コワイ! 慌ててググってみると、二項演算式と let 式で評価順序が違うらしいじゃないですか。しかもキャミバ様、もとい神々の使者ラクダ天狗(いや逆では?)もこう言ってるじゃないすか! やだー!

ここはドキュメントにあたらねば! -> 「評価順序は規定されていません」っつー文言が散見されます。 うーむ、 mjsk 。

と唸りながらドキュメントを眺めていたら、モジュール式にこうありました。

各定義はストラクチャ内に現れた順に評価されます。

OCaml のソースファイルはモジュールとしても扱われるので、えっと、つまり... let 式は「上から順に評価される」ということ? じゃあ冒頭の "Hello, world!" も順に評価されていただけで、 let () = を二行書いたら二行とも評価されるってこと? こ、こうか?

let () = Printf.printf "1\n"
let () = Printf.printf "2\n"
$ ocaml prog.ml
1
2

なんと...評価された。はー、だから OCaml には特別なエントリポイントは必要なかったわけだ。まあ(たいしてコード書いてないけど)これまで特に困った試しもないので、問題ないんでしょうね。

追記:不定の評価順序

評価順序についてコメントを頂きました。

別のところで書いたことがあるのですが、関数引数の評価順をあえて不定にするのにはそれなりの意味があります。ある特定の引数評価順に仕様を定めてしまうと、その評価順に依存した挙動のプログラムを書くことが堂々と可能かつ正当化されてしまいます。引数の評価順に依存したプログラムは複雑すぎるので書いて欲しくない場合、かつ依存しているかどうか静的に判定かつ禁止できない場合、言語設計者は評価順を積極的に不定にすることになります

なるほど、そういう理由があるんですね。で、ググってみたら C でも関数の評価順序は未規定じゃないですか。手元の本にも「引数の順序は指定されない」て書いてありました (演算子の式の評価順序も同じ) 。うわー、ずっと C 書いてたくせに今更知ったよ...

まあ実は OCaml での評価順序を気にし始めたのも、 MinCaml のソースをつい数日前に見たからなんですけどね。これまで OCaml の評価順序について考えたこともなかったよ (・ω<)

| exp EQUAL exp
    { Eq($1, $3) }
| exp LESS_GREATER exp
    { Not(Eq($1, $3)) }
| exp LESS exp
    /* ここから比較演算子を <= の式に変換。 $1 と $3 の式の位置を逆にしてる */
    { Not(LE($3, $1)) }
| exp GREATER exp
    { Not(LE($1, $3)) }
| exp LESS_EQUAL exp
    { LE($1, $3) }
| exp GREATER_EQUAL exp
    { LE($3, $1) }

よいこのみんな、勉強になったね!(お前だよ

Top comments (0)