Open9

OCaml 初学者メモ

hakotensanhakotensan

opam

https://opam.ocaml.org/

opamはOCamlのパッケージマネージャー。

Switch

https://ocaml.org/docs/opam-switch-introduction

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が使用される。

hakotensanhakotensan

ビルドシステム(Dune)

https://github.com/ocaml/dune

opam install dune

ビルドシステムにはDuneというツールが使われる。

プロジェクトの作成

opam exec -- dune init proj hello

ビルド

opam exec -- dune build
hakotensanhakotensan

演算処理

3 + 4;;
8 / 3;;
3.5 +. 6.;;
30_000_000 / 300_000;;
3 * 5 > 14;;

OCamlでは、int と floatは明確に区別する。

  • floatは 数値の小数点 .をつけなくてはならない。
  • 演算時にも . が必要
    • +., -., *., /.
hakotensanhakotensan

関数

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
hakotensanhakotensan

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;;
  • :: を複数組み合わせることで、先頭から複数の要素を取り出すことも可能
hakotensanhakotensan

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のようなイメージ