OCaml勉強会@axsh (はじめのいっぽ) Id:s1061123
公式ページにOcamlって書いてない!!!
公式ページはhttp://caml.inria.fr/index.en.htmlです.
Ocaml Objective Caml とは
• Objective Caml = Caml言語のObject拡張版
• CamlとはフランスのINRIAという研究所で開発された関数型言語で↓等々を供えた言語
– 型システム(型推論・多相型)
– パターンマッチング
• Ocamlはそれに↓を追加した言語
– モジュールシステム
– オブジェクト機能
– Native Compiler (ネイティブバイナリ吐ける!)
Objective Camlの親戚一覧
• ML系列 (Objective Camlと同類)– F# (for .Net) http://research.microsoft.com/en-
us/um/cambridge/projects/fsharp/default.aspx
– Standard ML of New Jersey (SML/nj) http://www.smlnj.org/
– Alice http://www.ps.uni-saarland.de/alice/
– SML# http://www.pllab.riec.tohoku.ac.jp/smlsharp/ja/
• Haskell
• Closure
• Concurrent Clean
• Lisp/scheme
特徴
• 型推論があるので(ある程度)プログラムのエラーを静的に解析できる
• パターンマッチによって複雑な条件式が簡潔に書ける
• 高階関数(Higher Order Function)や多相型(Polymorphic Type)によってより柔軟な処理の記述が可能
最終的なGoal
• Ocamlのプログラムのコンパイルの仕方,実行方法が分かるようになる
• 関数型言語の知識が(少し)分かるようになる (型推論とか,パターンマッチングとか…)
• 関数型言語っぽいプログラミングのマナーを知る(再帰脳・リスト指向・高階関数使用)
目次
• インストール
• “Hello world!”
– プログラムの起動・終了
– プログラムの解説
– その他
– 型について
• 関数
– 関数定義
– 条件分岐
– 再帰
• データ構造体
– リスト・ペア
– レコード
– バリアント
• パターンマッチ
– 再帰
• 多相型について
– 多相型関数
– 多相型データ
インストール
• Windows: Cygwin/MinGW/MSのどちからを選択.
– 違いはビルド環境
– ライブラリ,生成コードのインターフェイスに違い
– 詳しくは公式ページ(http://caml.inria.fr/ocaml/portability.en.html)
– ちなみに僕はMinGWをよくインストールします
– Tcl/Tk(Active TCL)をインストールすると吉
• Object Browser(リファレンス)が使えます
ベースの をインストールするという
インストール
• Mac:– Intel版を公式サイトからダウンロード
– MacPortsからインストール
• Unix/Linux:– ソースから頑張る
• gccインストールよりは確実に楽です
– パッケージはいろいろあります• Gentoo/Redhat/Debian for Linux
• Ports/pkgsrc for *BSD
解説
kagaribi% ocamlObjective Caml version 3.10.2
# print_string "hello world!";;hello world!- : unit = ()# ^Dkagaribi%
プログラムの入力
”^d”で終了
出力プログラムの
型
プロンプト
プログラム解説
# print_string “hello world!” ;;
関数名 引数 ターミネーター
• 引数渡す際にかっこ”()”やコンマ”,”は不要
• 文字列は””で囲む
• プログラムを評価したい場合には”;;”を末尾に
• 評価については2枚後で説明
• print_string は文字列を受けとって表示する関数
プログラム解説
• Print_stringの結果の表示の後に評価の結果を表示
• unitというのは型の一つでvoidみたいなもの
– 型についてはあとで説明
• “()”はunit型の値
• 他の例
hello world! - : unit = ()
標準出力 プロンプト結果の型 返り値
# 10 + 20;;- : int = 30#
“評価”とは?
• 与えられたOcaml式をコンピュータが解析すること
• 関数に全ての引数が与えられたら実行
kagaribi% ocamlObjective Caml version 3.10.2
# print_string “test string”;; ←関数に引数を加えて評価test string- : unit = () ←実行される# 10;; ←数字を評価-: int = 10 ←評価の結果は変わらず# print_string;; ←関数を評価すると?- : string -> unit = <fun> ←関数が返ってくる#
Type, Type, Type!!! (1)
• 型推論する関数型言語にとって型は命ですint : 31bit整数型 (Int32, Int64という型もあります)float : 不動小数点型 (doubleっぽい)string : 文字列型 (“test” で表記)char : 文字型 (‘a’ で表記)bool : true/falseunit : 値を返さない型.値は()のみT1 ->T2: 型T1の値を引数でT2の型の値を返す関数の型
Type, Type, Type!!! (2): 例
kagaribi% ocaml
Objective Caml version 3.10.2
# 10;;
- : int = 10
# 10.1;;
- : float = 10.1
# "test";;
- : string = "test"
# 'a';;
- : char = 'a'
# true;;
- : bool = true
# false;;
- : bool = false
# ();;
- : unit = ()
# print_string;;
- : string -> unit = <fun>
目次
• インストール
• “Hello world!”
– プログラムの起動・終了
– プログラムの解説
– その他
– 型について
• 関数
– 関数定義
– 条件分岐
– 再帰
• データ構造体
– リスト・ペア
– レコード
– バリアント
• パターンマッチ
– 再帰
• 多相型について
– 多相型関数
– 多相型データ
関数/条件分岐• 変数の定義・関数の定義
let foobar = 10;;let foobar = 15and foobar2 = 20;;
let testfunc a = print_string “answer is “;print_int (a + 30) ;;
let testfunc a b = a + b;;let testfun a:int = a;;
• スコープ付き変数・関数の定義let foobar = 10 in foobar + 10 ;;
• 名前無し関数(closure)定義fun a -> a + 10;;
let testfunc a b = a + b;; ===== let testfunc = fun a b -> a + b;;
連続した命令は”;”で分ける
これは再代入とは違うので注意!!(新規に領域を作って名前をbindしてい
る)
引数の型は明示的にも宣言可
関数/条件分岐
• If節の中に複数の命令を入れる場合は注意が必要
• 条件分岐
let testFunc a b =
if (a > b) then
(print_string “a > b”; a)
else
(print_string “a =< b”; b)
• 括弧の中を左から評価して最後の値を返す– 注意:最後以外はunitを返さないとwarning
• ()の代わりにbegin … endでもOK
繰り返し
• 繰り返しは再帰で書く.これ,最強.
– コンパイラが最適化しやすい
• 再帰する場合は”let” -> “let rec”で書きます
let rec testFun a =
if (a < 1) then
0
else
a + testFun (a-1)
• しかし再帰ってスタックオーバーフローするんじゃ…?
↑ > 末尾再帰(tail recursion)すれば大丈夫です
末尾再帰(tail recursion)の勧め
• 末尾再帰とは再帰の一種で再帰呼出以降に命令が存在しない再帰の呼び方
• イメージ的には再帰で返ってくる時に値を返す以外のことをしないような再帰
• この場合スタックは呼ばれた回数に比例して増大
A = 10 A = 0 A = 10
let rec testFun a = if (a < 1) then
0 else
a + testFun (a-1)
1+0 2+1 3+3 10+45 = 55
非末尾再帰
末尾再帰(tail recursion)の勧め
• 関数型言語の場合,末尾再帰の呼出し最適化によって再帰呼びだしは最適化されてジャンプ命令になる→スタック使わない
• よってスタックの増減は再帰回数に依存して増加しない
• 返すべき値を引数に渡すと案外簡単に末尾再帰に
A = 10 A = 0 A = 10
let testFunc a = let rec testFun1 a b =
if (a < 1) then b
else testFun1 (a-1) (b+a)
in testFun1 a 0
末尾再帰!
testFun1 10 0
testFun1 10 0testFun1 9 10testFun1 8 19…testFun1 0 55
testFun1 0 55 55
末尾再帰(tail recursion)の勧め
• 末尾再帰を思い付く(→再帰脳)は関数型言語の壁の一つ
• OcamlだけではなくHaskell, Closureでも使えるスキルなので是非!
let rec testFun a = if (a < 1) then
0 else a + testFun (a-1)# testFun 100000000;;Stack overflow during evaluation (looping recursion?).
let rec testFun a b = if (a < 1) then
belse
testFun (a-1) (b+a)# testFun 1000000000;;- : int = 5000000050000000
非末尾再帰で一億
末尾再帰で十億
目次
• インストール
• “Hello world!”
– プログラムの起動・終了
– プログラムの解説
– その他
– 型について
• 関数
– 関数定義
– 条件分岐
– 再帰
• データ構造体
– リスト・ペア
– レコード
– バリアント
• パターンマッチ
• 多相型について
– 多相型関数
– 多相型データ
リスト
• 関数型言語ではリスト(linked list)を基本データとして扱っています
# [10; 20; 30];;-: int list = [10; 20; 30]
# 10 :: [20; 30];;-: int list = [10; 20; 30]
# 10 :: (20 :: [30]);;-: int list = [10; 20; 30]
# 10 :: (20 :: (30 :: []));;- : int list = [10; 20; 30]#
10 20 30
car cdr
•リストは先頭の要素(car)と後続のリスト(cdr)で構成• []で空リストを表現•car と cdrの連結には “::”を使用
•リスト操作については解説しませんが一回再帰で書いておくと吉↑これも関数型言語共通のマナーです(リスト&再帰脳)
ペア
• 複数の型を組み合わせたペアの構造体
• 要素に名前はつかない
# (10, 20);;-: int * int = (10, 20)
# (10, 10.200);;-: int * float = (10, 10.2)
# (10, "teststr", 30.2);;-: int * string * float = (10, "teststr", 30.2)
# let pair = (10, "teststr", 30.2);;val pair : int * string * float = (10, "teststr", 30.2)
レコード
• C言語でいう構造体
# type pair_of_ints = { a : int; b : int; };;type pair_of_ints = { a : int; b : int; }
# {a = 10; b = 20};;- : pair_of_ints = {a = 10; b = 20}
# let p = {a = 10; b = 20};;val p : pair_of_ints = {a = 10; b = 20}# p.a;;- : int = 10
# let {a=k; b=l} = p;;val k : int = 10val l : int = 20
レコード定義c
書けば型は推論
取るときは”.”を使って
こうやってもOK
バリアント
• 関数型言語ならではの構造体
• イメージとしてはCのenum + union
typedef struct foobar_ { enum type_ {TEST_VOID,TEST_INT,TEST_FLOAT,TEST_CHAR } type;
union { int i;float d;char c;
} val;} foobar;
# type foobar = Void | Int of int | Float of float | Char of char | Pair of (int * int);; type foobar =
Void| Int of int| Float of float| Char of string| Pair of (int * int)
非常にシンプル!
バリアント# type foobar = Void | Int of int | Float of float | Char of string | Pair of (int * int);; type foobar =
Void| Int of int| Float of float| Char of string| Pair of (int * int)
# Void;;- : foobar = Void# Int 20;;- : foobar = Int 20# Pair (20, 20);;- : foobar = Pair (20, 20)
# None;;- : 'a option = None# Some 10;;- : int option = Some 10
使用例: option
パターンマッチ
let foobarToStr = functionVoid -> "void"
| Int i -> "Int(" ^ (string_of_int i) ^ ")"| Float f -> "Float(" ^ (string_of_float f) ^ ")"| Char c -> "Char(" ^ c ^ ")"| Pair (a, b) -> "Pair(" ^ (string_of_int a) ^ ", " ^ (string_of_int b) ^ ")";;
val foobarToStr : foobar -> string = <fun>
• データのパターン(型の構造)で条件分岐が可能!
# foobarToStr Pair(10,20);;This function is applied to too many arguments,maybe you forgot a `;'
# foobarToStr (Pair(10,20));;- : string = "Pair(10, 20)"
# foobarToStr Void;;- : string = "void"
int -> stringの変換:string_of_int文字列の結合: ^
パターンマッチ
• パターンマッチの方法は3種類# let p = Pair(10,20);;val p : foobar = Pair (10, 20)# let Pair(a,b) = p;;Warning P: this pattern-matching is not exhaustive.Here is an example of a value that is not matched:(Char _|Float _|Int _|Void)val a : int = 10val b : int = 20
# let p = (10,20);;val p : int * int = (10, 20)# let (a, b) = p;;val a : int = 10val b : int = 20# let (a, _) = p;;val a : int = 10#
しかしやや強引
単に値を取る場合
ペア等には有効
“_”で値を無視
パターンマッチ
# let foobarToStr a =match a with
Void -> "Void"| Int a when a > 100 -> "BigInt"| Int _ -> "Int"| _ -> "Others";;
val foobarToStr : foobar -> string = <fun>
# foobarToStr (Int 10);;- : string = "Int"# foobarToStr (Int 200);;- : string = "BigInt"
# let foob# let foobarToStr a =match a with
Void -> "Void"| Int _ -> "Int"| Int a when a > 100 -> "BigInt"| _ -> "Others";;
Warning U: this match case is unused.val foobarToStr : foobar -> string = <fun># foobarToStr (Int 200);;- : string = "Int"
When で条件追加
比較は上から順番なのでこの場合
BigIntが無視
多相型データ・関数
# let getRight (_, a) = a;;val getRight : 'a * 'b -> 'b = <fun>
ペアの右を返す関数
‘a , ‘b : “型”の変数で’どんな型が来ても構わない’ということ.‘a と’bが同じでも構わない.ただし一回目の’bと二回目の’bは同じ
# let getHead (h::t) = h;;Warning P: this pattern-matching is not exhaustive.Here is an example of a value that is not matched:[]val getHead : 'a list -> 'a = <fun># let getHead l = match l with (h::t) -> Some h | [] -> None;;val getHead : 'a list -> 'a option = <fun>
どんな型のリストも対応する関
数
多相型データ・関数
# type 'a pairs = { a : 'a ; b : int; };;type 'a pairs = { a : 'a; b : int; }
# type 'a tree = None | Node of ('a * 'a tree * 'a tree);;type 'a tree = None | Node of ('a * 'a tree * 'a tree)
# Node(10, Node(20, None, None), Node(30, None, None));;- : int tree = Node (10, Node (20, None, None), Node (30, None, None))#
多相型レコード
多相型バリアントで
バイナリツリー
10
20 30
お勧めリンク
• http://ocaml.jp/
• http://www.ocaml-tutorial.org/ja
マニア向けのリンク
• http://ocaml.janestreet.com/?q=node/13– (from http://d.hatena.ne.jp/camlspotter/20090906 )
• 余談:“Practical OCaml”という本は駄目らしいです