Open9
OCaml 初学者メモ
インストール(Mac)
brew install opam
opam init
opam
opamはOCamlのパッケージマネージャー。
Switch
opamにはSwitchという環境を切り替える仕組みがある。(pythonのvirtualenvのようなもの)
(switchの一覧)
opam switch
# switch compiler description
default ocaml.5.1.1 default
→ hello ocaml-base-compiler.5.1.1 hello
Switchの作成
opam switch create <名前> <バージョン>
(例)
opam switch create hello 5.1.1
Switch の更新
eval $(opam env)
ローカルSwichの作成
createコマンドで作成したswitchはデフォルトでglobalとして作成される。
特定のプロジェクト配下でのみ適用したい(プロジェクトへ移動したらそのSwitchを使いたい)場合は、local switchといして作成することもできる。
opam switch create . 5.1.1
swithcが作成されると _opam
というディレクトリが作成され、以後そのプロジェクトディレクトリへ移動された場合は、対象のswitchが使用される。
REPL(utop)
OCamlのREPLには utop
というツールを使う。
インストール
opam install utop
起動
opam exec utop
備考
一応 ocaml
コマンドでも標準のREPLが立ち上がるがutopの方がカスタマイズ性や補完性に優れている。
ビルドシステム(Dune)
opam install dune
ビルドシステムにはDuneというツールが使われる。
プロジェクトの作成
opam exec -- dune init proj hello
ビルド
opam exec -- dune build
Base
OCamlの標準ライブラリ。ビルドインの標準ライブラリを置き換えるために開発されている
演算処理
3 + 4;;
8 / 3;;
3.5 +. 6.;;
30_000_000 / 300_000;;
3 * 5 > 14;;
OCamlでは、int と floatは明確に区別する。
- floatは 数値の小数点
.
をつけなくてはならない。 - 演算時にも
.
が必要-
+.
,-.
,*.
,/.
-
関数
OCamlの関数は他の値の変数と同じく let
キーワードから始まる。
let sequare x = x * x;;
これは、 val sequare : int -> int = <fun>
となり、intを引数にしてintを返す関数となる。
(floatの場合は、*.
を使うため、この関数はintのシグネチャが確定する)
複数の引数
let ratio x y = Float.of_int x /. Float.of_int y;;
- 引数が複数ある場合は、
スペース
で区切られる
シグネチャは次のようになる。
val ratio : int/3 -> int/3 -> float = <fun>
型アノテーション
let ratio (x: int) (y: int) : float = Float.of_int x /. Float.of_int y;;
- 関数の型アノテーションを付けることもできる
ジェネリック型
let first_if_true test x y = if test x then x else y;;
このような関数があった時、シグネチャは次のようになる。
val first_if_true : ('a -> bool) -> 'a -> 'a -> 'a = <fun>
'a
の '
のがついている変数は、型が不定であり、ジェネリック型になる。
stringが入る例
let long_string s = String.length s > 6;;
first_if_true long_string "short" "loooooong";;
intが入る例
let big_number x = x > 3;;
first_if_true big_number 4 3;
名前付き引数
~<引数名>
で名前付き引数が使える。順番が自由に変更できる。
let sum ~a ~b = a + b;;
sum ~a: 3 ~b: 4;;
- : int = 7
sum ~b: 2 ~a: 10;;
- : int = 12
List
構文
let languages = ["OCaml"; "Perl"; "C"];;
- Listは
;
で区切る。(,
で区切るとタプルになってしまうため注意)
Base
- [[Base]] を使う場合はListモジュールにいろんな関数が入っている
let languages = ["OCaml"; "Perl"; "C"];;
List.length languages;;
::コンストラクタ
"Java" :: languages;;
-
::
でListの先頭に値を挿入した新しいListを作成できる。 - 先頭に追加できる要素は1つのみ。
配列の結合
-
@
を使うと配列を結合できる
[1; 2; 3] @ [4; 5; 6];;
- : int/2 list = [1; 2; 3; 4; 5; 6]
パターンマッチ
let rec sum l =
match l with
| [] -> 0
| hd :: tl -> hd + sum tl
- listは、
::
を使って、先頭の要素と残りでパターンマッチによる分割ができる- 慣習的に名前は
hd
,tl
(head, tail)を使う
- 慣習的に名前は
let l = [1; 2; 3; 4; 5; 6];;
let first :: second :: rest = l;;
-
::
を複数組み合わせることで、先頭から複数の要素を取り出すことも可能
Records・Variants
Record
type point2d = { x : float; y : float };;
let p = { x = 3.; y = 4. };;
val p : point2d = {x = 3.; y = 4.}
- type を使って名前付きのタプルを宣言することができるこれを
Record
という
パターンマッチ
let magnitude { x = x_pos; y = y_pos }
= Float.sqrt(x_pos **. 2. +. y_pos **. 2.);;
- 関数の引数にrecordをパターンマッチさせて変数展開する
let magnitude { x; y }
= Float.sqrt(x **. 2. +. y **. 2.);;
- わざわざ変数名を書かなくても良い。(上記と等価)
Variant
type circle_desc = { center: point2d; radius: float }
type rect_desc = { lower_left: point2d; width: float; height: float }
type segment_desc = { endpoint1: point2d; endpoint2: point2d }
type scene_element =
| Circle of circle_desc
| Rect of rect_desc
| Segment of segment_desc
- 複数のRecordを一つの型として表現することができる。これを
Variant
と呼ぶ。- 他言語のUnion TypeやSealed Typeのようなイメージ