イチから始める OCaml

szktty

SUZUKI Tetsuya

Posted on January 23, 2020

イチから始める OCaml

※この記事は 2017年03月28日 に Qiita に投稿したものです。


ゼロから始める OCaml」の続きです。元々は「やっぱり環境構築は無視できない道かなあ」と思っていたんですが、 Wandbox が Ocaml 、おっと OCaml だった OCaml に対応した (しかも Jane Street Core まで!) こともあり、環境構築は他のサイトなり記事なりを参考にしてもらうとして、 Ocaml 、おっと OCaml だった OCaml を始めたときに気になるであろうポイントをつつきたいと思います。

無料で読めるオススメのサイトはありますか?

なんだかもう恐ろしい経歴の方々が実用視点で執筆された Real World OCaml があります (紙の書籍も出てます) 。よくも悪くも Core に大きく依存する内容で Wandbox と相性がよいです。

英語がきつい? Google 翻訳にかけると恐ろしいクオリティで日本語でおk。人力の和訳が必要なくなるレベルなんじゃないかな、これは...

関数の型の読み方

OCaml の関数の型は "a -> b -> c" の形式で表されます。最後の "->" が戻り値です。 "a -> b -> c" であれば、引数 a, b を渡すと c の型の値が返ってきます。

関数の戻り値の型が期待と違う

前節で関数の型は "a -> b -> c" の最後の c が戻り値だと触れましたが、引数の数を間違えると c になりません。部分適用と言って、引数の数が満たされなくてもエラーにならないのではまりやすいポイントです。想定外の型エラーでビルドに失敗したら、該当の箇所の引数の数を確認してみましょう。

総称型の読み方

'a t という型は、 Java や TypeScript や Swift で言う総称型に相当します。 'a t なら t<a> です。アポストロフィ ' がつくのが型変数です。

'a とか t とかの 1 文字変数の意味

OCaml の型名は 'a とか t などの 1 文字の変数がよく使われます。これらの変数名は何の略語かと言うと、

  • 型変数 'a: ML 系の言語では a, b, c... とアルファベット順に続くのが慣習のようです。もちろん名前を付けたほうがわかりやすいので、そこはお好みで。

  • t: type の略です。 Module.t という型があったら、それは Module モジュールに所属する型 t を表します。

です。慣れないうちはわかりにくいかも。

ラベル付き引数とオプショナル引数

関数の引数には名前 (ラベル) をつけることができます (Swift にもありますね) 。型宣言だと引数の前にある f: がラベルです。注意すべきは、引数にラベルを付けて関数を呼び出す場合は ~ をつけないといけません。関数 f の型が x:int -> y:int -> int だったら、コードは f ~x:1 ~y:2 と書きます。

ラベルの前に ? があれば、それはオプショナル引数と言って、省略可能な引数です。詳しくはググるか本を読むかしてください。

コードの実行順序

OCaml は型推論のある強い静的型付けの言語です。静的型付けと言えば Java や Haskell 、最近ですと Swift や TypeScript を思い浮かべる方が多いかと思います。これらの言語は宣言の順序にわりと融通がきいて、型やクラスの宣言の場所や順序はユーザーに一任されていることが多いです。

「 OCaml も静的型付けの言語ですからそうだろう」と思いがちですが、実は OCaml のコードは 上から順に 評価されます。いいですね、 上から順に です。次のように、依存する関数をあとから書いたコードはコンパイルできません:

(* 内容は適当 *)
let f x = g x
let g x = x

正しくはこのように書きます。スクリプト言語みたいですね。

let g x = x
let f x = g x

相互に依存する型なり関数なりを書きたい場合はどうするのかって? and で定義をつなぎます。詳しくは各自調べてください。

ちなみに、関数に渡す引数の評価順序は 不定 です。関数 f を f x y z と呼び出したからと言って、引数の式が x, y, z の順に評価されるとは保証されません。

エントリーポイント

他の静的型付け言語だと main が一般的なエントリーポイント (最近はマイクロソフトの長音付けルールを参考にしてます) ですが、 OCaml では特別なエントリーポイントはありません。トップレベルに書いた式が 上から順に 実行されます。

ただし、トップレベルと関数定義内の式の文法は少し違い、区切り記号に ; だけでなく悪名高い ;; も使われます。ただし ;; は大抵の場合で省略可能で、言われるほど目にする機会はないと思います。私も滅多に書きません。

トップレベルに式を書く際に ;; を省略する方法は、式の値を let _ = .. と変数に代入するか、 let () = .. のように unit とパターンマッチさせます (もちろん、式の値が unit でなければ型エラーです) 。ちょっと面倒なバッドノウハウですね。

よければ OCaml のエントリポイントも参照してください。

;; の意味

上で触れたキモさ抜群の ;; ですが、これも上で触れましたが、トップレベルの式には文法上必要な記号です。と言うのは、関数式とトップレベルの let 式に終端記号がないからです。

例えば次のようにエントリーポイントを書きたいとします:

let main () =
    (* 適当 *)
    f ()

main ()

このとき、最後の main () はどういう扱いになるのか? OCaml の文法は改行に意味がないので、 f () にくっついて

f () main ()

という式に解釈されてしまいます。

それなら式の区切りに使う ; でどうでしょうか:

let main () =
    f ();

main ()

やっぱり改行に意味はないので、このように解釈されます:

let main () =
    f ();
    main ()

main ()main 関数内で呼ばれることになります。再帰関数に見えますが、 OCaml では let rec で始めないと再帰関数として扱われませんので、それはそれでエラーです。この例はたまたま再帰の形になってエラーになりますが、エラーにならない場合もあり、はまりポイントになってしまうことがあります。バッドノウハウ以外の何物でもありませんが気をつけましょう。

そういうわけで、トップレベルの終端記号である ;; を使えば問題は解決します。こうです:

let main () =
    f ()
;;

main ()

ただし、連続する let 式なら ;; は省略可能です。こう書けます:

let main () =
    f ()

let () = main ()

ちなみに let 式に end のような終端記号を必須にすれば ;; は不要になりますが、 end のないコードは快適でして、ほぼ省略可能な ;; を消すメリットは薄かったりします。

こればかりはキモいけど仕方ないかもしれませんね。

Wandbox で Core を使う

Wandbox が OCaml + Core に対応したことで、わざわざ環境構築しなくてもさっと試せるようになりました。 Core を使う場合はこうします:

  • "Jane Street Core" のチェックボックスを有効にする
  • コードの先頭に open Core.Std を書く

これで Core のモジュールを使えるようになります。 Core は OCaml の付属ライブラリのモジュールを同名のモジュールで置換するので、例えば List と書くと Core.Std.List が参照されます。

ただし、置換されるモジュール名や関数名は同じでも、 Core の API は引数のルールが OCaml 付属の API と違います。 Core の特徴は、

  • 1 モジュールにつき中心的な型は 1 つで、 t という名前で定義される
  • 第 1 引数の型はだいたい t
  • ラベルをよく使う
  • 多相バリアントをよく使う
  • モジュール名の規則は "Foo_bar_baz" (文法上、先頭は大文字でないといけない)

といったところです。最後のモジュール名などは「やっぱ OCaml の文法キモい」という悲鳴が聞こえますが、私なんかは「 CamelCase だと略語を大文字と小文字のどちらかにするかを考えるのが面倒くさい。 HTTP なのか Http なのかどっちがいいんだ」と思っちゃったんで、他の言語で例を見なくても "Foo_bar_baz" でいいじゃない、で済ませてます。

オブジェクト指向について

OCaml の "O" は元々 "Objective Caml" の "O" であり (現在は略語ではない一単語の "OCaml" に改名されています) 、関数型とオブジェクト指向のハイブリッドでコードを書けます。
もちろん OCaml の書籍でもオブジェクト指向について結構な量のページを割かれているので、普段オブジェクト指向言語を使っている人はまずそこから始めてみることが多いかもしれません。

しかし、実際は 実際は大半のユーザーがオブジェクト指向を使ってないと思います。 大半の状況でオブジェクト指向を使わなくても大丈夫だと思います。決して OCaml のオブジェクト指向の出来が悪いのではなく、理由は簡単です。オブジェクト指向って 意外と必要ない からです。必要ない理由はめんどくさいので割愛します (コード書くうちにわかるでしょ) 。オブジェクト指向のコーディングを覚えなくても全然問題ないので、参考書の該当の章は飛ばしてもいいと思います。

ファンクター難しそう

強力かつ複雑な、ファンクターという機能があります。ファンクターを簡単に言うと、コードを抽象化して大幅に無駄を減らせる (かもしれない) 機能です。なんですが、学習したてだとしばらくは必要ないはずです。まったく知らなくても問題ないです。「ここらへんは重複する処理が多くて抽象化できそう、でもやり方がわからない」と何度も痛感したら学ぶか、もしくは脳筋コピペで済ませてもいいと思います。余裕ができたらファンクターに取り組んでみましょう。ひとまずは動けばいいんです、動けば。

私は Real World OCaml や日本語書籍も含めて読んでもなかなかわかりませんでした。「やっぱ難しいんだ...」などとあきらめないで!というか、「最初からファンクターがわからなくてもなんとかなるんだ」とか「おいそこのレビュアー、俺にファンクターを教える権利をやろう」とか、ポジティブに前向きに考えていただけたらと思います。

ガチで取り組むなら何から始めればいい?

私も OCaml での業務経験はさほどないのであくまで個人的な意見ですが、コードを書く前にビルド環境の構築をおすすめします。

幸い、よくも悪くも OCaml ユーザーは世界中に大多数存在するとは言えず、ライブラリやツールの候補も多くありません (しかし、メジャーなものはいずれもハイクオリティです) 。というか余程のヘビーユーザーでなければほぼ一択じゃないでしょうか。最低限次のツールを揃えれば大丈夫だと思います。

  • パッケージ管理に OPAM
  • ビルドに OMake
  • 非力な組み込みライブラリを補助するライブラリ。とりあえず Core を使っておけば間違いはないです。でかいのが難点。

他にあるといいもの:

  • 生産性を上げるために OCamlSpotter
  • 足りないライブラリを作る気合い
  • (゚∀゚)人(゚∀゚)ナカーマ

がんばってください!

💖 💪 🙅 🚩
szktty
SUZUKI Tetsuya

Posted on January 23, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related