関数型言語
関数型言語 => プログラムの実行 = 式の実行
式の実行 => 式を評価(eval)して値(value)を得ること
基本データ型
整数型 (int)
プリフィックス
0b
で2進数
0o
で8進数
0x
で16進数
- : int = 351
# 12345;;
- : int = 12345
# 0o523;;
- : int = 339
# 0xffff;;
- : int = 65535
実数型 (float)
少数も扱える。
e表記も可能。
# 3.141;;
- : float = 3.1415
# 1.04e10;;
- : float = 10400000000.
# 25e-15;;
- : float = 2.5e-14
文字型 (char)
半角英数字(ascii文字) で1重引用符で囲む
# 'a';;
- : char = 'a'
# '\101';;
- : char = 'e'
# '\n';;
- : char = '\n'
文字列型 (string)
二重引用符で囲む。
文字列.[添字]
で1文字を取り出せる。
# "string";;
- : string = "string"
# "string".[0];;
- : char = 's'
型変換
X型からY型へ変換する関数は Y_of_X という命名規則がある。
# float_of_int 5;;
- : float = 5.
# int_of_float 5.;;
- : int = 5
- # string_of_int 123;;
- : string = "123"
# int_of_string "123";;
- : int = 123
組
(型名 * 型名...)
# (1, 2);;
- : int * int = (1, 2)
# ('a', 1, "str", 4.3);;
- : char * int * string * float = ('a', 1, "str", 4.3)
# ((1, 2), ('a', "str"));;
- : (int * int) * (char * string) = ((1, 2), ('a', "str"))
変数定義(束縛)
let定義 (let defenition)
let 変数名 = 式
# let hoge = 1;;
val hoge : int = 1
同時定義可能
let 変数名 = 式1 and 式2
# let a = 1 and b = 2;;
val a : int = 1
val b : int = 2
関数定義
let 関数名 パラメータ = 式
# let twice s = s ^ s;;
val twice : string -> string = <fun>
パラメータを囲む ()
は省略可能。
let式 (let expression)
let定義
とは違う。
関数内の一時的に使う変数(局所変数)を定義するための式。
let 変数 = 式1 in 式2
# let four_times s =
let twice = s ^ s in
twice ^ twice;;
val four_times : string -> string = <fun>
再帰定義
let rec で定義する。
rec
を使うと関数定義内で、定義中の関数の名前を参照できるようになる。
階乗の例
# let rec fact x =
if x <= 1 then 1 else x * fact (x - 1);;
val fact : int -> int = <fun>
# fact 5;;
- : int = 120
相互再帰
二つ以上の関数がお互いを呼び合うスタイルの再帰定義。
let rec 関数名1 パラメータ1 = 式1 and 関数名2 パラメータ2 = 式2 and ...
# let rec even n =
match n with
| 0 -> true
| x -> odd (x-1)
and odd n =
match n with
| 0 -> false
| x -> even (x-1);;
val even : int -> bool = <fun>
val odd : int -> bool = <fun>
# even 10;;
- : bool = true
# even 3;;
- : bool = false
# odd 3;;
- : bool = true
# odd 10;;
- : bool = false
無名関数
fun パラメータ = 式
let f パラメータ = 式
は let f = fun パラメータ = 式
の糖衣構文。
fun
は可能な限り先まで関数定義だと認識するため、必要なところまで ()
で区切ると良い。
高階関数
関数を引数に取る関数の定義
# let twice f x = f (f x);;
val twice : ('a -> 'a) -> 'a -> 'a = <fun>
# twice (fun x -> x * x) 3;;
- : int = 81
# let fourth x =
let square y = y * y in
twice square x;;
val fourth : int -> int = <fun>
# fourth 3;;
- : int = 81
関数のカリー化
# let concat_curry s1 = fun s2 -> s1 ^ s2 ^ s1;;
val concat_curry : string -> string -> string = <fun>
# concat_curry "a";; (* 部分適用 *)
- : string -> string = <fun>
# (concat_curry "a") "b";;
- : string = "aba"
カリー化の糖衣構文
以下の定義は
let concat_curry s1 s2 = s1 ^ s2 ^ s1;;
以下と同義
let concat_curry s1 = fun s2 -> s1 ^ s2 ^ s1;;
つまり、パラメータを並べると
# let fuga x y z = x + y + z;;
val fuga : int -> int -> int -> int = <fun>
実際には以下のように展開されている。
# let hoge x = fun y -> fun z -> x + y + z;;
val hoge : int -> int -> int -> int = <fun>
関数適用は左結合 なので、以下のように展開される。
f x y z => (((f x) y) z)
関数型構築子は右結合 なので、以下のように解釈する。
int -> int -> int -> int = <fun>
は
int -> (int -> (int -> int)) = <fun>
演算子の定義
中置演算子はカリー化された関数として定義されている。
# (+);;
- : int -> int -> int = <fun>
# (+) 1 2;;
- : int = 3
自分で演算子を定義する場合は
- 前置演算子
- 1引数関数として定義する
- 中置演算子
- 2引数(カリー化)関数として定義する
演算子の定義に使える文字種は制限されているので注意する。
型推論
比較演算子(=など)は整数型・実数型など複数の型で使える => **多相性(ポリモーフィズム)**を持つ。
多相性がある型システムを 多相的型システム と呼ぶ。
型スキーム・パラメトリック多相
型の出現パターンを示す、変数のようなもの。
'a
や 'b
のように表現される。
# let twice f x = f (f x);;
val twice : ('a -> 'a) -> 'a -> 'a = <fun>
# let first (x, y) = x;;
val first : 'a * 'b -> 'a = <fun>
実行の際に、OCaml の型システムが型スキーム('a
など)を具体化(具体的な型に当てはめる)する。
型情報をパラメータ化することによる多相性を パラメトリック多相 と呼ぶ。
型変数の一般化
型変数('_a
など)を一般化('a
のような型スキーム)するための条件の 1つ は以下。
定義の右辺の式がそのまま値であれば型変数を一般化してよい
値として扱われるもの => 計算を必要としない式
例として
- 関数の宣言
- 定数
値に評価されるために、計算が必要となる式は値として扱われない。
(* 恒等関数を定義する *)
# let id x = x;;
val id : 'a -> 'a = <fun>
(* '_a は型スキームではない
id id は値になるためには何か実引数が必要で、計算が必要。
*)
# let id' = id id;;
val id' : '_a -> '_a = <fun>
(*
こっちは型スキーム
パラメータ x を追加することで id' は関数定義となる。
*)
# let id' x = id id x;;
val id' : 'a -> 'a = <fun>
参考文献
非常に分かりやすく、OCamlを学ぶ際にオススメです。