(Rubyistのための) 超音速:ML入門 福盛秀雄 http://fukumori.org Ver.2005.07.30
準備運動 let rec factorial n = if n = 1 then 1 else factorial (n - 1) * n 階乗の計算 関数名 引数 let rec factorial n = if n = 1 then 1 else factorial (n - 1) * n
こんな書き方も let rec factorial = function | 1 -> 1 | n -> factorial (n - 1) * n 引数の「パターン」を記述
並べてみる let rec factorial n = if n = 1 then 1 else factorial (n - 1) * n let rec factorial = function | 1 -> 1 | n -> factorial (n - 1) * n 下の方が「ML的スタイル」
対話型環境を使ってみる $ ocaml Objective Caml version 3.08.3 #
ふつ~の計算 コロン二つで式の評価 # 123 + 456;; - : int = 579 結果の表示
ふつ~の計算!? 浮動小数点の足し算は +. # 123.0 +. 456.0;; - : float = 579. 普通じゃないよ!
「暗黙の型変換」? # 123.0 +. 456;; Characters 9-12: 123.0 +. 456;; ^^^ んなものは無い。 浮動小数点演算と 整数演算を混ぜてみる # 123.0 +. 456;; Characters 9-12: 123.0 +. 456;; ^^^ This expression has type int but is here used with type float (当然のように) エラーとなる
これならOK # 123.0 +. float_of_int 456;; - : float = 579.
変数の定義 # let a = 1;; val a : int = 1 # let x = “abc”;; val x : string = “abc” 定義された変数の型が表示される
“int -> int”型の関数“f”が定義された 関数の定義 関数の定義もlet 関数名、引数の順に記述 # let f x = x + 1;; val f : int -> int = <fun> “int -> int”型の関数“f”が定義された
(OCamlでは通常)スペースで区切って並べる 関数の定義(2) 複数の引数がある場合、 (OCamlでは通常)スペースで区切って並べる # let g x y = x * y;; val g : int -> int -> int = <fun> これをなんと読む? →「二つのintを順に受け取り、intを返す関数」
「関数」と「変数」の区別は? # let g = f;; val g : int -> int = <fun> 本質的には両者の間に明確な区別はない(?)。 関数を別の変数に束縛したり、 別の関数の引数にしたりすることもできる # let f x = x + 1 val f : int -> int = <fun> # let g = f;; val g : int -> int = <fun> # g 1;; - : int = 2 変数gに 関数fを束縛
Rubyのブロックに似ていないこともない 無名関数 名前の通り「名前の無い関数」 Rubyのブロックに似ていないこともない Ruby: {|x| x + 1} fun x -> x + 1 OCaml: ちなみにOCamlにて let f x = x + 1 と let f = fun x -> x + 1 は等価
付けないと “Unbound value factorial” 再帰を示す“rec” 再帰関数の定義には“rec”を付ける # let rec factorial n = if n = 1 then 1 else factorial (n - 1) * n;; val factorial : int -> int = <fun> 付けないと “Unbound value factorial” というエラーとなる
左辺のcounterと右辺のcounterは 関数型言語の特徴 # let counter = 0 ;; val counter : int = 0 # let count () = let counter = counter + 1 in counter;; val count : unit -> int = <fun> 変数への「代入」はできない 左辺のcounterと右辺のcounterは 別のもの counterは常に0のため count()の結果は常に1 # count ();; - : int = 1
関数型言語の特徴(2) ループは書けない(書かない) →再帰を使う 変数は変更できない →計算の途中経過は引数と 返り値に入れておく 変数はimmutable→代入はできない 関数は同じ引数に対して必ず同じ値を返す ループは書けない(書かない) →再帰を使う 変数は変更できない →計算の途中経過は引数と 返り値に入れておく
関数型言語の特徴(3) 「計算の途中経過は引数と 返り値に入れておく」 どうやって実現? 値の定義:強力なデータ型 「計算の途中経過は引数と 返り値に入れておく」 どうやって実現? 値の定義:強力なデータ型 値の参照:強力なパターンマッチング
リスト(list)とタプル(tuple) ;で区切る # [1;2;3];; - : int list = [1; 2; 3] タプル # (1,2,3);; - : int * int * int = (1, 2, 3)
リストのつくりかた # 1::[2;3];; - : int list = [1; 2; 3] # 1::2::3::[];; [1;2;3]と書くほかにも… 要素とリストをコロン二つで連結 # 1::[2;3];; - : int list = [1; 2; 3] []は空リスト # 1::2::3::[];; - : int list = [1; 2; 3]
レコード型 # type rt = {a : int; b : string};; “a”,”b”を持つ レコード型“rt”を定義 # type rt = {a : int; b : string};; type rt = { a : int; b : string; } # let rv = {a=1; b="xyz"};; val rv : rt = {a = 1; b = "xyz"} “rt”型の変数“rv”が定義された
ヴァリアント型 # type vt = Apple | Banana | Orange;; “ Cのenum”的な使い方 # type vt = Apple | Banana | Orange;; type vt = Apple | Banana | Orange # let vv = Apple;; val vv : vt = Apple
ヴァリアント型(2) # type vt2 = Ival of int | Fval of float;; 値つきのヴァリアント型を定義 # type vt2 = Ival of int | Fval of float;; type vt2 = Ival of int | Fval of float # let vvi = Ival 0;; val vvi : vt2 = Ival 0
パターンマッチング let rec factorial = function | 1 -> 1 整数値に対するパターンマッチの例 let rec factorial = function | 1 -> 1 | n -> factorial (n - 1) * n
“Pretty Print List” - 「文字列のリスト」を「文字列」へ変換 パターンマッチング(2) リストに対するパターンマッチの例 “Pretty Print List” - 「文字列のリスト」を「文字列」へ変換 let rec pp_list = function | [] -> "" | [x] -> x | x :: xs -> x ^ " " ^ pp_list xs xはリストの先頭 xsはリストの残り ^は文字列の連結 # pp_list;; - : string list -> string = <fun> # pp_list [“str1”;”str2”;”str3”];; - : string = “str1 str2 str3”
パターンマッチング(3) ヴァリアントに対するパターンマッチの例 type htmlstr = | UnSafe of string UnSafeはサニタイズされていないHTML文字列 Safeはサニタイズ済みのHTML文字列(のつもり) type htmlstr = | UnSafe of string | Safe of string let concat h1 h2 = match h1, h2 with | UnSafe(s1), UnSafe(s2) -> UnSafe(s1 ^ s2) | UnSafe(s1), Safe(s2) -> UnSafe(s1 ^ s2) | Safe(s1), UnSafe(s2) -> UnSafe(s1 ^ s2) | Safe(s1), Safe(s2) -> Safe(s1 ^ s2) 二つの引数に対する パターンマッチング
モジュール OCaml: Ruby: 『超』乱暴な説明: module Trig = struct let pi = 3.141592654 let sin x = ... let cos x = end module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) Trig.cos(0) => 1.0 # Trig.cos 0.0;; - : float = 1.
標準ライブラリ Listモジュールが 特によく使われるのでとりあえず紹介: 名前の通りリスト関連の 関数が定義されている
Rubyの“collect”イテレータと『ほぼ』同じ(?) List.map Rubyの“collect”イテレータと『ほぼ』同じ(?) Ruby: [1,2,3].collect {|x| x + 1} => [2, 3, 4] OCaml: # List.map (fun x -> x + 1) [1;2;3];; - : int list = [2; 3; 4]
Rubyの“inject”イテレータと『ほぼ』同じ(?) List.fold_left Rubyの“inject”イテレータと『ほぼ』同じ(?) Ruby: [1,2,3].inject(0) {|sum, element| sum + element} => 6 OCaml: # List.fold_left (fun sum element -> sum + element) 0 [1;2;3];; - : int = 6
“ref”で代入可能な変数(参照型変数)を定義 命令型処理 “ref”で代入可能な変数(参照型変数)を定義 # let counter = ref 0 ;; val counter : int ref = {contents = 0} # let count () = counter := !counter + 1; !counter ;; ;でつなげることにより 複数の式を順に評価 :=で代入(letが無いことに注意) !を付けると参照型変数の 実際の値が得られる # count ();; - : int = 1 - : int = 2
他にもいろいろありますが… あとは実践あるのみ。 MinCamlのソースコードを 読みに行きましょう。 ということで一旦お開き