インストールができたら、OCaml インタープリタを起動して色々と
遊んでみましょう。
以下では、>
はシェルのプロンプト、
#
はインタープリタのプロンプトです。
インタープリタでは、一つの文の終わりはゼミコロン二つ
;;
で表わします。
インタープリタからの返事は便宜上イタリックにします。
コメントは (*
と *)
の間に記述する
事ができ、C と違ってコメントをネストする事もできます。
let
構文で束縛されない値は
表示されて忘れ去られてしまうと考えて下さい
(インタープリタからの返事の行頭が '-')。
変数に束縛された場合にはインタープリタの返事の
行頭にその変数名が来ます。
> ocaml
Objective Caml version 3.06
#
OCaml はバイトコードインタープリタとネイティブコードコンパイラの 両方があります。学習の方法としては、わからない事があったらとりあえず インタープリタに打ってみて何を言われるか見てみる、と言うのが良いと思います。
どんなプログラミング言語を学ぶ時も、「Hello World!」を画面に表示する プログラムから始めるようですが、実は「画面に表示する」と言う行為は関数型 プログラミングが忌み嫌う(^^;べき「副作用」と言う部類に入るので、関数型 プログラミングがなんたるかを理解するまでは あまり教えたく無いのが本音なのですが、そんな事を言ってると初心者に「使えねー言語だな」などと思われてしまうと思うので、 とりあえずやってみます。
まずはインタープリタから:
# Pervasives.print_string "Hello World!\n";; Hello World! - : unit = ()
画面に、Hello World!
と表示されました。その後の
- : unit = ()
の解釈としては、-
が
今評価された値の変数名 (`-' の場合は無し)、その後の unit
がその値の型、そして =
の後の ()
が具体的な値と言う事になります。
今度はインタープリタに与えた入力を詳しく見ていきます。
Pervasives.print_string
は関数です。
この関数の型 (C で言うところのプロトタイプ?) は、
string -> unit
、つまり、string (文字列) 型を引数にうけとり、
unit 型 (void みたいなもの) の値を返す関数です。"Hello World!\n"
のように、ダブルクオートで囲まれた文字列は string 型の値を定義します。
関数の呼び出しは、C などとは違って引数を括弧で囲う必要がなく、
関数の後に並べるだけです。
Pervasives
と言うのはモジュールの名前です。
モジュールとは、関連する関数や値を集めたもので、
print_string
は Pervasives
モジュールの
中にあって、文字列を引数に受けとり、画面上にその文字列を出力する関数と
言う事になります。
OCaml ではこのようなモジュール名を通じて名前空間の分割をしています。
実は Pervasives モジュールは
いわゆる標準ライブラリの事で、Pervasives モジュールの中の関数は
例外的に Pervasives.
と書かなくても使えます。
上では説明の都合上付けました。
関数型言語の一つの特徴として、関数も普通の値もほとんど同様に扱う事ができる と言う点があります。インタープリタでこの事を見てみましょう。
# "Hello World!\n";; (* string 型の値 *) - : string = "Hello World!\n" # Pervasives.print_string;; (* 関数 型の値! *) - : string -> unit = <fun>
最初の、"Hello World!"
は string 型の値だと
インタープリタに解釈されました。次の
Pervasives.print_string
と言う値は、
型が string -> unit
で、<fun> は、この値が関数型である事を示しています。
このように全ての値は型を持ち、関数もまた「関数型」を持つ値にすぎません。
さて、上ではインタープリタでの結果を見ましたが、コンパイルする
場合は以下の内容を hello.ml
と言うファイルに保存して下さい。
(* Hello World program *) Pervasives.print_string "Hello World!\n";;
保存したら、ocamlc hello.ml -o hello
でバイトコードに
(ocamlopt hello.ml -o hello
でネイティブコードに)
コンパイルできます。コンパイルができたら実行してみましょう。
> ocamlc hello.ml -o hello > ./hello Hello World!
さて、OCaml では C や C++ のように最初に実行される特定の名前の main
関数みたいなものがありません。しかし、C や C++ と違って、関数の中でも関数の
定義ができたりするので、
プログラム全体が暗黙の main 関数の中にある と言う事ができます。
よって、上の例では暗黙の main 関数の中にある式:
Pervasives.print_string "Hello World!\n"
が評価されて、
(式の値自体は unit 型: () であるが)
その副作用で画面に Hello World! の文字が出ました。
ちょっとオマケですが、print_string
があれば、print_int
もあり、それぞれの基本型用の print_*
関数があります。
しかしながら、これでまとまったテキストを出力しようとしても無理があります。
実は C の printf
と同じような関数 Printf.printf
があるのですが、
この関数は型付規則を例外的に処理していて、私はちゃんと説明
できません (^^; が、以下のようにも使う事ができます。
# Printf.printf "%d hoge %s foo %b\n" 3 "bar" false;; 3 hoge bar foo false - : unit = ()
もう少し複雑な例を見てみましょう。quick sort を OCaml で書いてみるとどうなるかを見てみましょう。 (以下ではリストと言う基本データ構造に対する quick sort を考えます)
標準ライブラリにはもちろんソートする関数 List.sort があるので、 自分でsort関数を書く必要はほとんどありません。
let rec quicksort = function
[] -> [] (* base case *)
| hd::tl ->
let (lt, gt) = List.partition (fun i -> i < hd) tl in
(quicksort lt)@[hd]@(quicksort gt);;
要は quick sort のアルゴリズムの定義そのままで書いています。 C の qsort 関数で考えるソートとの微妙な意識の違いは、 「与えられたリスト(配列)の要素を昇順に並び換える」のではなく、 「(元のリストはそのままに)与えられたリストの要素が昇順に並んだ、新しいリストを返す」 のが目的だと言う事です。 コードの意味を詳しく見ていきましょう。
しかし短かいですが、ちゃんとしたコードなのでしょうか? インタープリタで見てみましょう。
# let rec quicksort = function
[] -> [] (* base case *)
| hd::tl ->
let (lt, gt) = List.partition (fun i -> i < hd) tl in
(quicksort lt)@[hd]@(quicksort gt)
val quicksort : 'a list -> 'a list = <fun>
しかし、この quicksort 関数、
変数の定義をするにも、変数の「型」をまったく指定しませんでした。
良いのでしょうか? OCaml では新しい型を定義したりする場合や、
特別な場合を除いて変数の型が何であるかを指定する必要は無いのです。
コンパイラが変数がどのようにして使われているのかを調べて、勝手に推論してくれます。
今回の場合は、変数 quicksort の型は'a list -> 'a list = <fun>
であると推論してくれました。つまり 'a list を受けとり、'a list を返す関数型の値です。
この 'a と言うのは、多相型、つまり、どんな型でも良いと言う事を意味しています。
ですので、int list であろうが、float list であろうが、string list であろうが、
この quicksort と言う関数が使える、と言う事になります。
# quicksort [5;4;2;-1;9;0;3];;
- : int list = [-1; 0; 2; 3; 4; 5; 9]
# quicksort ["hoge"; "foo"; "bar"; "alongstring"; ];;
- : string list = ["alongstring"; "bar"; "foo"; "hoge"]
上ではこれは比較演算子 (<) に固定されていましたが、 違う比較演算でソートができないものでしょうか。 以下のように cmp と言う新しい比較関数を引数として取るように書換えてみました。
# let rec quicksort cmp = function
[] -> [] (* base case *)
| hd::tl ->
let (lt, gt) = List.partition (fun i -> cmp i hd) tl in
(quicksort lt)@[hd]@(quicksort gt)
val quicksort : ('a -> 'a -> bool) -> 'a list -> 'a list = <fun>
# quicksort (* 文字列の長さ(だけ)でソート *)
(fun x y -> (String.length x) < (String.length y))
["hoge"; "foo"; "bar"; "alongstring"; ];;
- : string list = ["foo"; "bar"; "hoge"; "alongstring"]
# quicksort (* 文字列の長さ(だけ)でソート *)
(fun x y -> (String.length x) < (String.length y))
[5;4;2;-1;9;0;3];; (* 整数には無理ってものよ *)
Characters 66-67:
[5;4;2;-1;9;0;3];;
^
This expression has type int but is here used with type string
どうでしょう。関数型言語のウリの一つ、 関数が値のように簡単に、かつ型のおかげで安全に受渡しができているのがわかるでしょうか。