OCamlで簡単なプログラムを書いてみる

プログラミング言語のひとつであるOCamlで簡単なプログラムを書いてみる。

環境

Ubuntu 16.04.2 LTS 64bit版、OCaml 4.02.3

$ uname -a
Linux vm-ubuntu64 4.4.0-66-generic #87-Ubuntu SMP Fri Mar 3 15:29:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.2 LTS
Release:        16.04
Codename:       xenial

$ ocaml -version
The OCaml toplevel, version 4.02.3

OCamlの概要

OCamlはINRIA(フランス国立情報学自動制御研究所)によって開発されたプログラミング言語であり、関数型言語MLの方言であるCamlにオブジェクト指向の要素が取り入れられたものである。 他の関数型言語と同様に、静的な型システム・型推論、パターンマッチ、末尾呼び出し最適化等を備えている。

関数型言語として比較的近いものにHaskellがあるが、Haskellとの違いのひとつに評価戦略がある。 Haskellが遅延評価であるのに対し、OCamlは正格評価である。 遅延評価には不必要な計算が行われないという利点がある一方で、未評価の式(thunk)が溜まるとパフォーマンスが落ちる(スペースリーク)という難点がある。 OCamlは正格評価であるため、正格評価を行う多くのプログラミング言語と同様に考えることができる。 なお、Haskellseq$!で正格評価を扱うことができる(参考)ように、OCamllazyによって遅延評価を扱うことができる。

OCamlが使われているプロジェクトとしては、以下が挙げられる。

OCamlのインストール

Ubuntuの場合、標準リポジトリからパッケージをインストールできる。 ocaml-noxX11サポートなし、ocamlX11サポートありである。

$ sudo apt install ocaml-nox

なお、OCamlにはパッケージマネージャとしてOPAMがあるが、ここではインストールしないことにする。

ocamlコマンドでインタプリタを起動し、適当な計算を行ってみる。 インタプリタでは、;; で式の終わりを表す。

$ ocaml
        OCaml version 4.02.3

# let rec sigma a b f =
    if a > b then 0 else f a + sigma (a+1) b f;;
val sigma : int -> int -> (int -> int) -> int = <fun>
# sigma 1 10 (fun x -> x*x);;
- : int = 385
# [Ctrl+D]

上のコードでは、Σ_{i=1}^10 i^2 = 1^2 + 2^2 + ... + 10^2 = 385 を計算している。 OCamlにおいて、匿名関数(ラムダ式)は (fun PARAMETER_1 ... PARAMETER_n -> EXPR) と書く。

簡単なプログラムを書いてみる

プログラムの例として、素因数分解を行う factor.ml を書いてみる。

let factor n =
  let rec aux d n =
    if n < d*d then [n]
    else if n mod d = 0 then d :: aux d (n/d)
    else aux (d+2) n in
  let rec aux2 n =
    if n < 2 then []
    else if n mod 2 = 0 then 2 :: aux2 (n/2)
    else aux 3 n in
  aux2 n

(* entrypoint *)
let () =
  let n = int_of_string Sys.argv.(1) in
  (* let n = int_of_string @@ read_line () in *)
  let factors = factor n in
  print_endline @@ (string_of_int n) ^ ": " ^ (String.concat " " @@ List.map string_of_int factors)

factor は一通り2で割った後、平方根以下の奇数で試し割りを行う関数である。 OCamlにはC言語におけるmain関数のような概念がないため、慣習的に let () = ... 等でエントリポイントが表わされる。 コメントは (* *) で表し、C++における // のような1行コメントはない。 また、^ は文字列連結を行う演算子@@Haskell$ に相当し、g (f x) == g @@ f x のように括弧を避ける用途で用いられる演算子である(参考)。

ocamloptコマンドで実行ファイルにコンパイルし、実行してみると次のようになる。

$ ocamlopt factor.ml -o factor

$ file factor
factor: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4a20beb80735f3abf4a7f6d85b343a7a737fcb87, not stripped

$ ./factor 360
360: 2 2 2 3 3 5

$ time ./factor 4611686018427387903
4611686018427387903: 3 715827883 2147483647

real    0m4.946s
user    0m4.944s
sys     0m0.000s

$ factor 4611686018427387903
4611686018427387903: 3 715827883 2147483647

上の結果から、正しく実行ファイルにコンパイルされ、素因数分解できていることが確認できる。

なお、64 bit環境においてOCamlのintは63 bit符号付き整数であり、-262から262-1までの値となる。 64 bitのうちの1ビットは内部でガベージコレクション用に利用される(参考)。

$ ./factor 4611686018427387904
Fatal error: exception Failure("int_of_string")

$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> bin(4611686018427387904)
'0b100000000000000000000000000000000000000000000000000000000000000'
>>> len(_)-3
62
>>> [Ctrl+D]

クラスを使ったプログラムを書いてみる

OCamlらしいプログラムの例として、クラスを使った oop_example.ml を書いてみる。

class virtual animal (name : string) = object(self)
  val mutable name = name
  method set_name new_name = name <- new_name
  method virtual cry : unit
end

class dog name = object(self)
  inherit animal name
  method cry = print_endline @@ name ^ ": bowwow"
end

class cat name = object(self)
  inherit animal name
  method cry = print_endline @@ name ^ ": meow"
end

let () =
  let dog = new dog "Snoopy" in
  let cat = new cat "Kitty" in
  dog#cry;
  cat#cry;
  dog#set_name "Pochi";
  cat#set_name "Tama";
  dog#cry;
  cat#cry

animal#cry のような仮想メソッドを持つクラスには、クラス本体にも virtual キーワードを付ける必要があることに注意する。

コンパイルして実行すると次のようになる。

$ ocamlopt oop_example.ml -o oop_example

$ ./oop_example
Snoopy: bowwow
Kitty: meow
Pochi: bowwow
Tama: meow

関連リンク