OCamlを自習する。 浅井健一著『プログラミングの基礎』とそのWebサイト http://pllab.is.ocha.ac.jp/~asai/book-mov/ を使う。
UTF-8で漢字の入出力に対応させるのがわりと面倒。
UbuntuなどDebian系のGNU/Linuxにはapt-get
で簡単にインストールできる:
sudo apt-get install tuareg-mode ocaml ocaml-findlib ocaml-doc
$HOME/.emacs
には
;;; tuareg for OCaml
(load "tuareg")
を加える。
opamを使う方法がOCaml: 環境設定Debian 8向けに書かれている。
tuaregもEmacs 24.4でM-x package-install tuareg
とインストールしている。
opamを使ってOCamlのコンパイラのアップグレードは
opam update
opam upgrade --unlock-base
とする。たぶん。
MacPortsでインストールするのは、Graphicsモジュールが入らなかったり、opamでごにょごにょしないといけなかったり面倒。 結局、ソースからインストールした。X11はXQuartzを使った。
wget http://caml.inria.fr/pub/distrib/ocaml-4.02/ocaml-4.02.3.tar.xz
wget http://caml.inria.fr/pub/distrib/ocaml-4.02/MD5SUM
md5 ocaml-4.02.3.tar.xz | grep 9115706e30dad644f8dec9dfb459a9ab
tar xf ocaml-4.02.3.tar.xz
cd ocaml-4.02.3
cat INSTALL
./configure -x11include /opt/X11/include -x11lib /opt/X11/lib
make world
make bootstrap
make world.opt
sudo make install
hash -r
which ocamlc.opt
Graphicsを使わないのであれば、opamを使ってOCamlをインストール/アップグレードしていくのが便利そう。
最新のtuaregもインストールしてみる。
(opam install tuareg
でインストールされるtuareg 2.0.8はEmacs 24で
Error during redisplay: (void-function tuareg-syntax-propertize)
とかいうエラーで動かなかった。)
$ git clone https://github.com/ocaml/tuareg.git tuareg
$ cp tuareg/*.el ~/COMMON/share/emacs/24/site-lisp/
$HOME/.emacs
に次を書き加える。
;;; tuareg for OCaml
(load "tuareg-site-file")
WindowsのCygwin(gnupackを使ってインストールしたもの)
にOCamlをインストールするのはapt-cyg
で簡単:
$ apt-cyg install ocaml
tuaregはMacと同じようにすれば、gnupack付属のEmacsで困難なく使えた。
漢字 (UTF-8) を扱えるようにするために、$HOME/.ocamlinit
に
(* -*-Tuareg-*-
This file is for UTF-8 input/output of OCaml. See
http://d.hatena.ne.jp/murase_syuka/20150814/1439506123
https://github.com/murasesyuka/dotfils/blob/5f6d38e617a3109e5bd6dbe216c40831c693475c/.ocamlinit
*)
let printer ppf = Format.fprintf ppf "\"%s\"";;
#install_printer printer
を書き加える。意味は不明。
何をするためのものなのか、いまいち不明。Ubuntuにはapt-get
でインストールしたもの。
ocamlfind
はcamlcityからFindlibのソースを取ってきて、
tar xf findlib-1.6.1.tar.gz
cd findlib-1.6.1
cat INSTALL
./configure
make all
make opt
sudo make install
OCamlのREPL環境は
toplevel system や
トップレベル対話環境
と呼ばれるocaml
コマンドである。
しかしながら、履歴機能がなかったりdeleteキーが^H
と表示されたりと、必ずしも使いやすいものではない。
(C-?で1文字消して戻ることはできる。)
$ ocaml
OCaml version 4.02.3
# let average a b =
(a +. b)/. 2.0;;
val average : float -> float -> float = <fun>
# average 2.0 3.0;;
- : float = 2.5
# "雲古";;
- : string = "雲古"
# ^D
ocaml インタプリタでコマンドの履歴を利用するに書かれているとおり、
ledit
を使うと入力の履歴が使えるが、utf-8(日本語、漢字)の入力が化ける。出力は化けないでうまくいく。
EmacsでtuaregはM-x tuareg-run-ocaml
で起動。
M-p, M-nで履歴が使える。エラーに色がついたり、マウスが使えたり、
また、foo.ml
というファイルを編集するとキーワードに色がついたりして便利。
何もしなくても漢字が表示される。上記の$HOME/.ocamlinit
の設定のおかげなのかは不明。
(OCaml 4.11.1 と Emacs 27.1 の組み合わせでは
M-x set-buffer-process-coding-system
でoutput/inputをutf-8
を指定する必要はなくなったみたい。)
住井英二郎著 数理科学的バグ撲滅方法論のすすめ 第1回 OCamlを試してみる
のとおりgraphics.cma
をロード(#load, インタラクディブにダイナミックリンク)して円が描けた。
M-x tuareg-run-ocaml
OCaml version 4.02.3
# "雲古";;
- : string = "雲古"
# #load "graphics.cma" ;;
# Graphics.open_graph "" ;;
- : unit = ()
# Graphics.draw_circle 100 100 50 ;;
- : unit = ()
# #quit;;
tuareg-kill-ocaml (C-c C-k
) でも終了できる。
以下では、このtuareg上で、浅井健一著『プログラミングの基礎』 と http://pllab.is.ocha.ac.jp/~asai/book-mov/ を使って自習を進める。
整数は加減乗除+
, -
, *
, /
と余りmod
の計算ができる。
これらを含め基本的な演算は Module Pervasives
に定義されていて、毎回自動的にopenされる。
したがって、その構成要素はすべてPervasives
をつけずに参照することができる。
# 7-3*4;;
- : int = -5
# 7/2*2;;
- : int = 6
# 7*2/2;;
- : int = 7
# 7 mod 3;;
- : int = 1
# -7 mod 3;;
- : int = -1
OCamlでは+もmodも関数で、実は中置演算子というのは糖衣構文 (syntactic sugar)。
# (+);;
- : int -> int -> int = <fun>
# (+) 3;;
- : int -> int = <fun>
# List.map ((+) 2) [1; 2; 3];;
- : int list = [3; 4; 5]
負の定数には前置演算子(?)~-2
や~-.0.16
を用いる必要があることもある。
# let bmi height weight = weight /. height ** 2.0;;
val bmi : float -> float -> float = <fun>
# bmi 1.77 68.9;;
- : float = 21.9924032046985225
# bmi 1.77;;
- : float -> float = <fun>
# bmi 1.77 -. 0.01;;
Characters 0-8:
bmi 1.77 -. 0.01;;
^^^^^^^^
Error: This expression has type float -> float
but an expression was expected of type float
# bmi (1.77 -. 0.01);;
- : float -> float = <fun>
# bmi 1.77 ~-.70.01;;
- : float = -22.3467075233808927
実数は加減乗除+.
, -.
, *.
, /.
とベキ乗**
の計算ができる。
# 7.0/.2.0;;
- : float = 3.5
# 2.0 *. 3.14 *. 10.0;;
- : float = 62.8000000000000043
# 1.73 ** 2.0;;
- : float = 2.9929
# 7.0 /. 2.0;;
- : float = 3.5
# infinity *. -0.1;;
- : float = neg_infinity
# -0.0;;
- : float = -0.
# infinity *. 0.0;;
- : float = nan
# 2.0 *. 2;;
Characters 7-8:
2.0 *. 2;;
^
Error: This expression has type int but an expression was expected of type float
文字列は^
で結合できる。
# "東京" ^ "特許" ^ "許可局" ^ "局長";;
- : string = "東京特許許可局局長"
# "関数" ^ "型" ^ "言語";;
- : string = "関数型言語"
'A'
などと指定する。
Char標準モジュールがある。
# 'A';;
- : char = 'A'
# Char.code 'A';;
- : int = 65
# "ABC".[1];;
- : char = 'B'
true
or false
。
&&
, ||
, not
。
同じ型のものは比較できても異なる型のものは比較できない。
# 2 > 3;;
- : bool = false
# not(3.1415**2.0 > 10.0);;
- : bool = true
# 3+4+5 = 4*3;;
- : bool = true
# true > false;;
- : bool = true
# 3.14 > 3;;
Characters 7-8:
3.14 > 3;;
^
Error: This expression has type int but an expression was expected of type float
NaNが含まれる実数の比較はいつもfalseが返ることを利用してNaNの判定をしてみる。 引数が実数あるという制限 (type constraint) をかけて、実数の比較が呼ばれるようになっている。 詳しくは https://groups.google.com/forum/#!topic/fa.caml/ZVxHLBYEMwA を見よ。
# nan > 1.0;;
- : bool = false
# nan < 1.0;;
- : bool = false
# 1.0 < nan;;
- : bool = false
# 1.0 > nan;;
- : bool = false
# infinity > nan;;
- : bool = false
# nan > infinity;;
- : bool = false
# nan = nan;;
- : bool = false
# let is_nan (x:float) = not (x = x);;
val is_nan : float -> bool = <fun>
# is_nan (1.0 /. 0.0);;
- : bool = false
# is_nan (infinity /. 0.0);;
- : bool = false
# is_nan (infinity *. 0.0);;
- : bool = true
unit型は()という値のみを持つ型。
C言語でいうところのvoid。
forループやString.iterは()を返さないといけない。
:=
は()を返す。
次の変数をOCamlインタプリタで定義せよ。それぞれの変数の型は何か。
# let e = 2.7182;;
val e : float = 2.7182
# let positive = e > 0.0;;
val positive : bool = true
# let seconds_of_day = 60*60*24;;
val seconds_of_day : int = 86400
# let name = "茗荷谷";;
val name : string = "茗荷谷"
こういうのを表示してくれる仕組みはないのかな。trace?
1.0 +. e *. 2.0
1.0 +. 2.7182 *. 2.0
1.0 +. 5.4364
6.4364
アルバイトを始めたときには時給850円だが1年経過するごとに時給が100円ずつあがることにしよう。
アルバイトを始めてからの年数とその月に働いた時間が与えられたら、
その月の給与を返す関数baito_kyuyo
を定義せよ。
(引数以外の変数は関数の定義の前に定義されていなくてはならない。)
# let baito_kyuyo years hours = kihonkyu + hours * (jikyu + 100 * years);;
Characters 24-32:
let baito_kyuyo years hours = kihonkyu + hours * (jikyu + 100 * years);;
^^^^^^^^
Error: Unbound value kihonkyu
# let kihonkyu=100;;
val kihonkyu : int = 100
# let jikyu = 850;;
val jikyu : int = 850
# let baito_kyuyo years hours = kihonkyu + hours * (jikyu + 100 * years);;
val baito_kyuyo : int -> int -> int = <fun>
# baito_kyuyo 1 25 + baito_kyuyo 1 28 + baito_kyuyo 1 31;;
- : int = 80100
名前を与えたら、適当な自己紹介文を返す関数。
# let jikoshokai name = "私の名前は" ^ name ^ "です。";;
val jikoshokai : string -> string = <fun>
# jikoshokai "松子";;
- : string = "私の名前は松子です。"
標準体重。
# let hyojun_taiju height = height ** 2.0 *. 22.0;;
val hyojun_taiju : float -> float = <fun>
# hyojun_taiju 1.77;;
- : float = 68.9238
BMI。
身長だけを入れると、引数が1つだけの関数が返される。
つまりval bmi : float -> (float -> float) = <fun>
ということ。
# let bmi height weight = weight /. height ** 2.0;;
val bmi : float -> float -> float = <fun>
# bmi 1.77 68.9;;
- : float = 21.9924032046985225
# bmi 1.77;;
- : float -> float = <fun>
「強く型付けされた言語」でも test first で書くのね。
# #use "tsurukame.ml";;
val tsurukame : int -> int -> int = <fun>
val test1 : bool = true
val test2 : bool = true
val test3 : bool = true
ここではタプル、レコード、リストを説明。 バリアント型と配列は後半で節を改めて説明。
タプルは("Paul", "Dirac", 1902, 8, 8)
のようにカンマで区切る。
カッコは必須ではないが、混乱を避けるため付けておく。
_
がワイルドカード。
# let dirac = ("Paul", "Dirac", 1902, 8, 8);;
val dirac : string * string * int * int * int = ("Paul", "Dirac", 1902, 8, 8)
# 1900 < match dirac with (_,_,year,_,_) -> year;;
- : bool = true
パターンマッチが使える。Erlangみたいなことができる?
# #use "seiseki.ml";;
val seiseki : string * int -> string = <fun>
val test1 : bool = true
val test2 : bool = true
# seiseki ("桃子", 77);;
- : string = "桃子さんの評価は 77点です"
type
で定義。{}
でくくる。フィールドとその値。
フィールドは他のレコードのフィールドと重なってはいけない。
フィールドの順番は自由だが、フィールドを省略することはできない。
教科書『プログラミングの基礎』では.
(ドット)を使ったフィールドの参照は非推奨。
# type gakusei_t = {
name : string;
tensuu : int;
seiseki : string;
};;
type gakusei_t = { name : string; tensuu : int; seiseki : string; }
# let asai = {
name = "浅井";
tensuu = 70;
seiseki = "B";
};;
val asai : gakusei_t = {name = "浅井"; tensuu = 70; seiseki = "B"}
# asai.seiseki;;
- : string = "B"
# let kiyo = {name = "清原";};;
Characters 11-32:
let kiyo = {name = "清原";};;
^^^^^^^^^^^^^^^^^^
Error: Some record fields are undefined: tensuu seiseki
tsuuchi.ml。
tuareg-eval-buffer
(C-c C-b) を使ってみる。
# type gakusei_t = {
name : string;
tensuu : int;
seiseki : string;
}
let tsuuchi gakusei = match gakusei with
{name=n; tensuu=t; seiseki=s} ->
{name=n; tensuu=t;
seiseki= if t>=80 then "A"
else if t>=70 then "B"
else if t>=60 then "C" else "D"}
(* tests *)
let test1 = tsuuchi {name="花子"; tensuu=100; seiseki=""} = {name="花子"; tensuu=100; seiseki="A"}
let test2 = tsuuchi {name="太郎"; tensuu= 77; seiseki=""} = {name="太郎"; tensuu= 77; seiseki="B"};;
type gakusei_t = { name : string; tensuu : int; seiseki : string; }
val tsuuchi : gakusei_t -> gakusei_t = <fun>
val test1 : bool = true
val test2 : bool = true
OCaml入門(3) レコード、バリアント、例外、参照も参考になる。 参照を使ったリストの連結の定義がナゾ。
標準ライブラリのComplex
はこのレコードで実装されている。
日本語のマニュアル、
英語のマニュアル。
# open Complex;;
# mul {re=1.0; im=1.0} {re=1.0; im=1.0};;
- : Complex.t = {re = 0.; im = 2.}
要素の型は1つに限る。
::
がcons演算子。@
が連結。
操作はList Moduleに定義されている。
car
, cdr
はそれぞれList.hd
, List.tl
(headとtail)だが、
パターンマッチングでhead :: tail
を使うのが定石。
OCaml 標準ライブラリ探訪 #2 List : スタックと計算量に注意
が参考になりそう。
# 1 :: [];;
- : int list = [1]
# 1 :: 2 :: [];;
- : int list = [1; 2]
# List.length [1; 2];;
- : int = 2
# List.sort compare [1; 3; 2];;
- : int list = [1; 2; 3]
# [1;2;3] @ [4;5;6];;
- : int list = [1; 2; 3; 4; 5; 6]
受け取ったリストに0が含まれているかを判別する contain_zero.ml。
# let rec contain_zero lst = match lst with
[] -> false
| first :: rest ->
if first = 0 then true
else contain_zero rest
(* test *)
let test1 = contain_zero [] = false
let test2 = contain_zero [0; 1; 2; 3] = true
let test3 = contain_zero [1; 2; 0; 3] = true
let test4 = contain_zero [1; 2; 3; 4] = false;;
val contain_zero : int list -> bool = <fun>
val test1 : bool = true
val test2 : bool = true
val test3 : bool = true
val test4 : bool = true
受け取ったリストの長さを返す。
普通の: length.ml。
末尾再帰: length_tail_rec.ml。
この中のlet NAME = EXPRESSION1 in EXPRESSION2
が局所定義 (the local definition)。
EXPRESSION1
はEXPRESSION2
の中でだけ使える。
普通は標準ライブラリ list.ml
の中に定義されているList.length
を使えばよい。
整数のリストのを受け取り、その中の偶数の要素のみを含むリストを返す。
even_tail_rec.ml。 これだと順番が逆になるけど。
ちなみにList.filter
を使うとこんな感じ(funを使わなくて済む方法はないのか):
# List.filter (fun x -> 0 = x mod 2) [2; 1; 6; 4; 7];;
順番が保たれる回答: even.ml。
@
を使った。function
も使ってみた。
なお、ここでやっている通り、OCamlではxs @ [x] したら負け。
list.mlのfilter
の実装にある通り、最後にList.rev
すること。
最後にList.rev
することにして、さらにifを後ろに移した回答:
even_if.ml。
こうしたほうが、コンパイラが末尾再帰にしやすいとか、よいのだろうか。
文字列のリストを受け取り、その要素すべてを結合した文字列を返す。 concat.ml。
レコードのリストgakusei_t listを受け取って、A評価の人の数を数える。 count_A.ml。
受け取ったリストの「接頭語」のリストを返す。なんでこれが「接頭語」なのか。
prefix.ml。
List.map
に(fun l -> first :: l)
を渡してみた。::
は特別な演算子なのか、((::) first)
と書けない?
挿入ソート。insert sort。ins_sort.ml。
参考: http://www.geocities.jp/m_hiroi/func/ocaml04.html パターンマッチングでas
を使ってる
リストの中の最小値を求める関数。
min_in_list.ml。
max_int = 4611686018427387903
を使っている。[]
に対応できない→例外処理。
http://www.codecodex.com/wiki/Merge_sort からほとんどコピペで merge.mlを書いた。
末尾再帰には『プログラミングの基礎』第16章に説明がある accumulator を用いるのが便利である。
sum_list.ml。 受け取った整数のリストのそこまでの和のリストを返す。 3つの方法で実装した。
factorial.mlと
fact.mlとを
ocamlopt -S -c
でコンパイルして、アセンブラ出力factorial.s
とfact.s
とを比較すると、
factorial.s
はjmp
なのがわかる。
OCamlではオーバーフローしても例外は発生しないし、Rubyのように自動的に多倍長整数にもならない。
多倍長整数モジュールBig_intをインタラクディブに使うには
#load "nums.cma";;
とタイプしてnumライブラリをロードする。
open Big_int
すればBig_int.mult_int_big_int n a
などのBig_int.
を省略できる。
こんなかんじでfactorial_big 200
も一瞬で計算できる:
# let factorial n =
let rec factorial_helper (n, a) =
if n = 0 then a else factorial_helper (n - 1, a * n)
in
factorial_helper (n, 1);;
val factorial : int -> int = <fun>
# factorial 5;;
- : int = 120
# factorial 100;;
- : int = 0
# #load "nums.cma";;
# open Big_int
let factorial_big n =
let rec factorial_big_helper a n =
if n = 0 then a else factorial_big_helper (mult_int_big_int n a) (n-1)
in
factorial_big_helper unit_big_int n;;
val factorial_big : int -> Big_int.big_int = <fun>
# string_of_big_int (factorial_big 5);;
- : string = "120"
# string_of_big_int (factorial_big 100);;
- : string =
"933 ... 000"
# string_of_big_int (factorial_big 200);;
- : string =
"788 ... 000"
# power_int_positive_int 10 42 |> string_of_big_int;;
- : string = "1000000000000000000000000000000000000000000"
fbig.mlはBig_int
を使った実行形式をつくるコードで、
そのコンパイル方法をこの文章の終わりの方に書いた。
末尾再帰にしたfibonacci.mlなら、
fibonacci 90;;
なども一瞬でできる。91以上はオーバーフローする。
a.ml。
一般項はa**(i+1)+1
だけど、その自動証明とかに使えるのかな?
list.mlにあるように、 match式によるパターンマッチングはfunction文を使うと簡単になる。 function 文は匿名関数とmatch式を組み合わせたもの。 参考: お気楽OCamlプログラミング入門 パターンマッチング
# let rec length_aux1 len = function
[] -> len
| head::tail -> length_aux1 (len + 1) tail;;
val length_aux1 : int -> 'a list -> int = <fun>
# let rec length_aux2 len lst = match lst with
[] -> len
| head::tail -> length_aux2 (len + 1) tail;;
val length_aux2 : int -> 'a list -> int = <fun>
# let length1 lst = length_aux1 0 lst;;
val length1 : 'a list -> int = <fun>
# let length2 lst = length_aux2 0 lst;;
val length2 : 'a list -> int = <fun>
# length1 [];;
- : int = 0
# length1 ["aaa"; "bbb"; "ccc"];;
- : int = 3
# length2 [1; 2; 3; 4; 5];;
- : int = 5
これまで引数を2つ以上とる関数を使ってきたが、これはカリー化によって表現されていた。 また、カリー化によって表現された関数は引数の部分適用ができる。
次の2つの匿名関数は等価。
# fun x y -> x + y;;
- : int -> int -> int = <fun>
# fun x -> fun y -> x + y;;
- : int -> int -> int = <fun>
参考1: OCamlのお勉強 その5 ~カリー化、高階関数、匿名関数~
参考2: OCamlチュートリアル > 関数型プログラミング
カリー化(もしかしてアンカリー化)と高階関数(汎関数)とList.map
と匿名関数 (anonymous function, nameless function) と。
# let plus a b =
a + b;;
val plus : int -> int -> int = <fun>
# plus;;
- : int -> int -> int = <fun>
# plus 2;;
- : int -> int = <fun>
# plus 2 3;;
- : int = 5
# List.map (plus 2) [1; 2; 3];;
- : int list = [3; 4; 5]
# List.map (fun x -> x*x) [-1; 0; 1; 2; 3];;
- : int list = [1; 0; 1; 4; 9]
List.fold_left2
を使って2つの浮動小数点数リストのドット積を計算する。
List.fold_left2
の定義通りの順番に引数を置いておけばよい。
# List.fold_left2;;
- : ('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a = <fun>
# List.fold_left2 (fun a b c -> a +. b *. c) 0.0 [0.2; 0.3] [-0.3; 0.2];;
- : float = 0.
# let dot_product = List.fold_left2 (fun a b c -> a +. b *. c) 0.0;;
val dot_product : float list -> float list -> float = <fun>
# dot_product [0.707106781186547; 0.707106781186547]
[0.707106781186547; 0.707106781186547];;
- : float = 0.999999999999998557
高階関数は関数を受け取って、関数を返してもよい。
# let f1 x = x;;
val f1 : 'a -> 'a = <fun>
# let f2 x y = x;;
val f2 : 'a -> 'b -> 'a = <fun>
# let f3 x y = y;;
val f3 : 'a -> 'b -> 'b = <fun>
# let f4 x f = f x;;
val f4 : 'a -> ('a -> 'b) -> 'b = <fun>
# let f5 f g = let h x = (g (f x)) in h;;
val f5 : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c = <fun>
関数を2つ受け取ったら、それらを合成した関数を返す関数compose
。
# let time2 x = 2 * x;;
val time2 : int -> int = <fun>
# let add3 x = 3 + x;;
val add3 : int -> int = <fun>
# let compose f g = let h x = f (g x) in h;;
val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
# (compose time2 add3) 4;;
- : int = 14
quick_sort_partition.ml、
ちょっとだけクイックソート - チラシの裏 よりList.partition
を使って。
さらに、結合 (append, '@') を使わないのquick_sort_no_append.ml。すごい。
エラトステネスの篩 eratosthenes.ml。
List.filter
を使わないで、first
, 2*first
, 3*first
, ...
を落として行くようにすれば速くなるはずだがやり方がわからない。
「どれかひとつ」を表す型。
構成子 (Tag) は大文字から始まらなくてはならない。
type <variant> =
| <Tag> [ of <type> [* <type>]... ]
| <Tag> [ of <type> [* <type>]... ]
| ...
17章の例で時刻をタプルに変更してみた:
# type jikoku_t =
| Gozen of int * int
| Gogo of int * int
| Noon
| Midnight;;
type jikoku_t = Gozen of int * int | Gogo of int * int | Noon | Midnight
# Gozen (10,10);;
- : jikoku_t = Gozen (10, 10)
# Noon;;
- : jikoku_t = Noon
# Gogo 10;;
Characters 0-7:
Gogo 10;;
^^^^^^^
Error: The constructor Gogo expects 2 argument(s),
but is applied here to 1 argument(s)
# Gogo (10,30);;
- : jikoku_t = Gogo (10, 30)
さらにレコードに (record) に変更してみた:
# type jikoku_rec = {hour:int; minute:int};;
type jikoku_rec = { hour : int; minute : int; }
# type jikoku_variant =
| Gozen of jikoku_rec
| Gogo of jikoku_rec
| Noon
| Midnight;;
type jikoku_variant =
Gozen of jikoku_rec
| Gogo of jikoku_rec
| Noon
| Midnight
# Noon;;
- : jikoku_variant = Noon
# Gogo ({hour=10; minute=10});;
- : jikoku_variant = Gogo {hour = 10; minute = 10}
# Gozen {hour=10; minute=10};;
- : jikoku_variant = Gozen {hour = 10; minute = 10}
値を全て2倍にした木を返す tree_double.ml
木構造の全てのエレメントに関数fを施す tree_map.ml
参照型let sum = ref 0
とか、
値の書き換え:=
(()
を返す。)とか。
dnc.mlではfor
ループを使っている。
「ズン」「ドコ」のいずれかをランダムで出力し続けて
「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら
「キ・ヨ・シ!」って出力した後終了って関数、
ズンドコキヨシを
ズンドコOCaml(cnt >= 4
にすること)を参考に
print_endline
をたくさん使って普通に書き直してみた:
zundoko.ml
Random.bool
はunit -> bool
な関数で、
Unix.gettimeofday
はunit -> float
な関数で、
それらに引数の()
を与えて初めて欲しいものが出てくるのだな。
配列は書き換えを前提にして作られたデータ構造。 Arrayモジュールが使える。
# let a = [| 0.0; 1.1; 0.0; 0.0 |];;
val a : float array = [|0.; 1.1; 0.; 0.|]
# Array.set a 2 2.2;;
- : unit = ()
# a.(3) <- 3.3;;
- : unit = ()
# a;;
- : float array = [|0.; 1.1; 2.2; 3.3|]
# Array.fold_left (+.) 0.0 a;;
- : float = 6.6
# Array.fold_right (+.) a 0.0;;
- : float = 6.6
整数の配列にフィボナッチ数を入れて返す fib_array.ml。
引数が状態の関数をtail callし続ければよいのではないか。
参考1: How to represent a simple finite state machine in OCaml?
参考2: automata in OCaml
参考3: grammarlearning
参考4: Tree automaton
もちろん Pervasives に書いてある。
ここにも http://rigaux.org/language-study/syntax-across-languages-per-language/OCaml.html 。
::
は特別な演算子なのか、((::) first)
と書けない?
@
は2つのリストをくっつける。list1 @ list2
はlist1
の長さに比例した時間がかかる。
Reverse-application operator: x |> f |> g
is exactly equivalent to g (f (x))
.
Application operator: g @@ f @@ x
is exactly equivalent to g (f (x))
.
# let minus x y = x - y;;
val minus : int -> int -> int = <fun>
# minus 5 6;;
- : int = -1
# 6 |> minus 5;;
- : int = -1
# minus 5 2 + 4;;
- : int = 7
# minus 5 @@ 2 + 4;;
- : int = -1
OCamlの標準ライブラリと一緒に配布されてる
numライブラリの中には
Num, Big_int, Arith_statusの3つのモジュールが入っている。
コマンドライン引数から1つ整数を読み込んでその階乗を標準出力に印刷するプログラム
fbig.mlでは、
Big_intを使うためにそのnums.cma
をリンクしている。
$ ocamlc nums.cma fbig.ml -o fbig
$ ./fbig 36
371993326789901217467999448150835200000000
OCamlチュートリアル モジュールを参考にした。
$ head -22 Makefile h*.ml*
==> Makefile <==
#-*-Makefile-*- for hello
##
hello: hmodule.cmx hello.cmx
ocamlopt -o $@ $^
%.cmx: %.ml
ocamlopt -c $<
%.cmi: %.mli
ocamlc -c $<
hmodule.cmx: hmodule.cmi
hello.cmx: hmodule.cmx
clean:
rm -f *.cmx *.cmi *.o hello
==> hello.ml <==
Hmodule.hello ()
==> hmodule.ml <==
let message = "Hello!"
let hello () = print_endline message
==> hmodule.mli <==
val hello : unit -> unit
(** Displays a greeting message. *)
$ make
ocamlc -c hmodule.mli
ocamlopt -c hmodule.ml
ocamlopt -c hello.ml
ocamlopt -o hello hmodule.cmx hello.cmx
$ ./hello
Hello!
$ ocamlopt -o hello hello.cmx hmodule.cmx
File "_none_", line 1:
Error: No implementations provided for the following modules:
Hmodule referenced from hello.cmx
最後に例示したように、リンクの順序に意味があることに注意が必要。
OCamlには特にエントリポイントというものはなく、 式が現れた順に評価される。 上のようにリンクの順序に意味があるのも多分そのため。
次のような1行だけのprog.mlが動くのは ファイルの最後でたまたまセミコロンが省略可能だった式が普通に評価されるから。 2行にするならprog2.mlのように1つか2つのセミコロンが必要。
$ head prog.ml prog2.ml
==> prog.ml <==
print_endline "Hello, world!"
==> prog2.ml <==
print_endline "Hello, world!";
print_endline "Hello, universe!"
$ ocaml prog.ml
Hello, world!
$ ocaml prog2.ml
Hello, world!
Hello, universe!
OCamlMakefileは 複雑なOCamlプロジェクトのコンパイル作業を簡単化するツール。 ホームページやGitHubから入手できる。
上のhello用のMakefileは次の通りで、
make
(make bc
と等価でバイトコードの実行ファイルを作成),
make nc
(ネイティブコードの実行ファイルを作成), make clean
などができる。
#-*-Makefile-*- for hello-with-OCamlMakefile
##
RESULT = hello
SOURCES = hmodule.mli hmodule.ml hello.ml
OCAMLMAKEFILE = OCamlMakefile
include $(OCAMLMAKEFILE)
fbig.ml用はLIBS
を使う。
#-*-Makefile-*- for fbig-with-OCamlMakefile
##
RESULT = fbig
LIBS = nums
SOURCES = fbig.ml
OCAMLMAKEFILE = OCamlMakefile
include $(OCAMLMAKEFILE)
make clean && make bc && /usr/bin/time ./fbig 100000 > /dev/null
make clean && make nc && /usr/bin/time ./fbig 100000 > /dev/null
make clean && ocamlopt.opt nums.cmxa -o fbig fbig.ml && /usr/bin/time ./fbig 100000 > /dev/null
の3つで、速度がたいして違わないのはどうしたわけなんだ。
Findlib(コマンド名はocamlfind
)
を使うとライブラリのありかを探してくれるらしい。
詳しくは Native-code compilation (ocamlopt) や Batch compilation (ocamlc) などを参照せよ。
- .ml OCamlソース
- .mli インターフェース。例えば、list.mlに対して、 外部に公開する関数を羅列しているのがlist.mli。
- .cmi コンパイルされたインターフェース情報。
- .cmo バイトコードのオブジェクトファイル。
ocamlc -c foo.ml
でできる。 - .cmx ネイティブコードのオブジェクトファイル。
ocamlopt -c foo.ml
でできる。 - .cma ライブラリ。バイトコード。
- .cmxa ライブラリ。ネイティブコード。
- .cmxs OCaml plugin file???
- .cmt
ocamlc -bin-annot foo.ml
でできるやつ - .o ネイティブオブジェクトコード。
ocamlopt -c foo.ml
でできる。.cmx
があるので、利用されることはほぼない。 - .s アセンブラ出力。
ocamlopt -S -c factorial.ml
でできる。 - .mll ocamllexのソースコード。
- .mly ocamllexのソースコード。
トップレベルシステムであるocaml
コマンドのの引数には、
そのまま**.cmoオブジェクトファイルや.cmaライブラリを与えることもできる。
また、トップレベル内では#load "foo.cmo";;
で.cmoオブジェクトファイルや.cma**ライブラリを、
#use "bar.ml";;
でソースファイルを読み込むことができる。
#load
や#use
などはトップレベル環境でのみ使用できる。
詳細はToplevel system (ocaml)を見よ。
- やっと関数型言語をひととおり学習できた。手続き型言語やRubyの知識があって、 Eelangでパターンマッチングをいじったことがあって、 Schemeで何度も挫折していたので、なんとか浅井健一著『プログラミングの基礎』を最後まで…
- これを学部2年生でやるのはすごい。
- インストールの敷居が高い。UTF-8で漢字の入出力に対応させるのがわりと面倒。
- ところどころで「停止性」について言及しているが、いまいちピンとこない。
- 末尾再帰とか末尾最適化とかは 必須ではないし、そんなにこだわらなくてよいみたい。 『プログラミングの基礎』では言及なし。
- コンマやLispのような括弧がないので、
関数の引数を
fibonacci_helper next (next+current) (n-1)
などと書かなければいけないときに括弧を忘れがち。
[WARNING] BFD library not found, 'objinfo' will be unable to display info on .cmxs files.
Assembler supports CFI
** Configuration summary **
Directories where OCaml will be installed:
binaries.................. /usr/local/bin
standard library.......... /usr/local/lib/ocaml
manual pages.............. /usr/local/man (with extension .1)
Configuration for the bytecode compiler:
C compiler used........... gcc
options for compiling..... -O -Wall -D_FILE_OFFSET_BITS=64 -D_REENTRANT
options for linking....... -lcurses -lpthread
shared libraries are supported
options for compiling..... -O -O -Wall -D_FILE_OFFSET_BITS=64 -D_REENTRANT
command for building...... gcc -bundle -flat_namespace -undefined suppress -Wl,-no_compact_unwind -o lib.so /a/path objs
Configuration for the native-code compiler:
hardware architecture..... amd64
OS variant................ macosx
C compiler used........... gcc
options for compiling..... -O -D_FILE_OFFSET_BITS=64 -D_REENTRANT
options for linking.......
assembler ................ clang -arch x86_64 -c
preprocessed assembler ... clang -arch x86_64 -c
assembler supports CFI ... yes
with frame pointers....... no
naked pointers forbidden.. no
native dynlink ........... true
profiling with gprof ..... supported
Source-level replay debugger: supported
Additional libraries supported:
unix str num dynlink bigarray systhreads threads graph
Configuration for the "num" library:
target architecture ...... amd64 (asm level 1)
Configuration for the "graph" library:
options for compiling .... -I/opt/X11/include
options for linking ...... -L/opt/X11/lib -lX11
** OCaml configuration completed successfully **