LoginSignup
4
1

More than 3 years have passed since last update.

OCaml 入門その6 ラベル付き引数・オプション引数・多相ヴァリアント

Last updated at Posted at 2018-01-02

その5

ラベル付き引数

~ラベル名:

引数に名前を付けることができる。
ラベル名をつければ引数の順を好きに変えれる。

(* ラベル付き引数で関数を定義する *)
# let rec range ~first:a ~last:b =
    if a > b then []
    else a :: range ~first:(a+1) ~last:b;;
(* 関数の型にラベル名が付く *)
val range : first:int -> last:int -> int list = <fun>

(* ラベル名を指定して関数適用 *)
# range ~first:1 ~last:10;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
# range ~last:10 ~first:1;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

(* ラベル名を指定しなければ定義順で適用 *)
# range 1 10;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
# range 10 1;;
- : int list = []
  • ~hoge:○○○○ は省略可能
    • 定義時のみ ~ を付ける
# let rec range ~first ~last =
    if first > last then []
    else first :: range (first + 1) last;;
val range : first:int -> last:int -> int list = <fun>

オプション引数

?ラベル名:(パターン=式)

Python のデフォルト引数と同じ機能。

(* デフォルト1 で step値を与えられる *)
# let rec range ?(step=1) a b =
    if a > b then []
    else a :: range ~step (a+step) b;;
val range : ?step:int -> int -> int -> int list = <fun>

(* 関数適用 *)
# range 1 10;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
(* 呼び出し時はラベルを指定するので順不同 *)
# range 1 10 ~step:2;;
- : int list = [1; 3; 5; 7; 9]
# range 1 ~step:3 10;;
- : int list = [1; 4; 7; 10]

(* オプション引数はラベル省略不可 *)
# range 2 1 10;;
Error: The function applied to this argument has type ?step:int -> int list
This argument cannot be applied without label

オプション引数の注意点

オプション引数の後には、ラベルなし引数を用意する

オプション引数が関数の最後にあると、省略ができないオプション引数となってしまい、意味がなくなるので注意。

(**
 * 定数1を返す関数を定義したいが…。
 * オプション引数が最後にあると警告が出る
 *)
# let f ?(x=1) = x;;
Warning 16: this optional argument cannot be erased.
val f : ?x:int -> int = <fun>
(* 1が返ると思ったら関数が返る
 * オプション引数を受け取る関数がカリー化されて返ってくる
 *)
# f;;
- : ?x:int -> int = <fun>

(* なので最後にラベルなし引数を追加しないとだめ *)
# let f ?(x=1) () = x;;
val f : ?x:int -> unit -> int = <fun>
# f;;
- : ?x:int -> unit -> int = <fun>
# f();;
- : int = 1

オプション引数の実体

オプション引数は 'a option で実現されている。

デフォルト値を指定せず ?hoge とだけ書いて実行してみると、以下の通りエラーがでる

# let rec range ?step a b =
    if a > b then []
    else a :: range ~step (a+step) b;;
(* 'a option の型エラーがでている *)
Error: This expression has type 'a option
       but an expression was expected of type 'a
       The type variable 'a occurs inside 'a option

よってこの場合は None'a Some でパターンマッチする

(* option 型でパターンマッチ *)
# let rec range ?step a b =
    let s = match step with None -> 1 | Some s -> s in
    if a > b then [] else a :: range (a + s) b;;
val range : ?step:int -> int -> int -> int list = <fun>

# range 1 10;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

多相ヴァリアント

`コンストラクタ

複数のヴァリアント型で共通のコンストラクタを使用できるようにする仕組み。
「1コンストラクタ <=> 1ヴァリアント」の制限をなくす。

# `Hoge;;
- : [> `Hoge ] = `Hoge
# `Hoge 2;;
- : [> `Hoge of int ] = `Hoge 2
# `Hoge `Fuga;;
- : [> `Hoge of [> `Fuga ] ] = `Hoge `Fuga

多相ヴァリアントを返す関数

# let f b = if b then `Hoge else `Fuga;;
val f : bool -> [> `Fuga | `Hoge ] = <fun>
# let hoge = function
  | `Hoge -> "hoge"
  | `Fuga -> "fuga"
  | `Piyo -> "piyo";;
val hoge : [< `Fuga | `Hoge | `Piyo ] -> string = <fun>

多相ヴァリアントの型スキーム

[> ...] , [< ... ] は型スキーム ('a と同じ扱い)

[> ..][< ..] は制限付きの型変数として扱われる。

[> ...] の場合

[> は「含まれる多相ヴァリアント型 以上」と解釈できる。

(* 
 * なんでも受け取れる多相ヴァリアントのリスト
 *)
# let  a : [>] list = [`Fuga; `Piyo];;
val a : [> `Fuga | `Piyo ] list = [`Fuga; `Piyo]

# a @ [`Asdf];;
- : [> `Asdf | `Fuga | `Piyo ] list = [`Fuga; `Piyo; `Asdf]
# let f' = function
  | `Hoge -> `Fuga
  | `Fuga -> `Hoge
  | x -> x;;
val f' : ([> `Fuga | `Hoge ] as 'a) -> 'a = <fun>

(* `Hoge, `Fuga 以上の型を受け取る *)
# f' `Asdf;;
- : [> `Asdf | `Fuga | `Hoge ] = `Asdf

[< ...] の場合

[< は「含まれる多相ヴァリアント型 以内」と解釈できる。

(* `Hoge, `Fuga 以内 *)
# let f = function
  | `Hoge -> "hoge"
  | `Fuga -> "fuga";;
val f : [< `Fuga | `Hoge ] -> string = <fun>

(* `Hoge, `Fuga 以内の型 *)
# type type_A = [`Hoge | `Fuga];;
type type_A = [ `Fuga | `Hoge ]
# let a:type_A = `Hoge;;
val a : type_A = `Hoge
# f a;;
- : string = "hoge"

(* `Hoge, `Fuga 以上の型 *)
# type type_B = [`Hoge | `Fuga | `Piyo];;
type type_B = [ `Fuga | `Hoge | `Piyo ]

(* `Hoge だけど type_B は `Hoge, `Fuga 以上の型 *)
# let b:type_B = `Hoge;;
val b : type_B = `Hoge
# f b;; (* 型エラー *)
Error: This expression has type type_B but an expression was expected of type
         [< `Fuga | `Hoge ]
       The second variant type does not allow tag(s) `Piyo

多相ヴァリアントの型定義

  • [> ..][< ..] は使えないパターン
    • type hoge = [`Hoge | `Fuga | ...]
  • 型変数を宣言して [> ..][< ..] を定義するパターン
    • type 'a hoge = [> `Hoge ...] as 'a
# type hoge = [`Hoge | `Fuga];;
type hoge = [ `Fuga | `Hoge ]

# type 'a hoge = [> `Hoge] as 'a;;
type 'a hoge = 'a constraint 'a = [> `Hoge ]

# type 'a hoge = [< `Hoge] as 'a;;
type 'a hoge = 'a constraint 'a = [< `Hoge ]

(* 型定義を再利用可能 *)
# type hoge' = [hoge | `Piyo];;
type hoge' = [ `Fuga | `Hoge | `Piyo ]

多相ヴァリアントの再帰定義

type 'a mylist = Nil | Cons of 'a * 'a mylist を多相ヴァリアントで定義してみる。

  • まずは順当に定義してヴァリアント型がどうなるか確認する
(* 順当に定義してヴァリアント型がどうなるか確認する *)
# let l1 = `Nil;;
val l1 : [> `Nil ] = `Nil
# let l2 = `Cons(1, `Nil);;
val l2 : [> `Cons of int * [> `Nil ] ] = `Cons (1, `Nil)

(* 一般項的な型定義を得られることはない *)
# let l3 = `Cons(2, `Cons(1, `Nil));;
val l3 : [> `Cons of int * [> `Cons of int * [> `Nil ] ] ] =
  `Cons (2, `Cons (1, `Nil))
  • 任意のリスト処理を行う関数を書けば、リストの一般項的な型定義を得られるはず…
(* 再帰ヴァリアント型の型定義は…? *)
# let rec length = function
  | `Nil -> 0
  | `Cons (a, l) -> 1 + length l;;

(*
 * [ ... ] as 'a としてヴァリアント型の定義に別名が付与されており
 * その別名 'a が [...] の中に現れており、再帰的定義であることが分かる
 * 'b によって与えられる型変数が一種類に固定されることもわかる
 *)
val length : ([< `Cons of 'b * 'a | `Nil ] as 'a) -> int = <fun>
(* リスト内の最大値を選ぶ関数 *)
# let rec max_list = function
  | `Cons(x, `Nil) -> x
  | `Cons(x, `Cons(y, l)) ->
    if x < y then max_list (`Cons(y, l)) else max_list (`Cons(x, l));;

(*
 * [ .. [ .. ] ] という形をしているため、長さが1以上であることが分かる
 *)
val max_list : [ `Cons of 'a * ([< `Cons of 'a * 'b | `Nil ] as 'b) ] -> 'a = <fun>

多相ヴァリアントの型付けの注意

「&」に注意

もし多相ヴァリアントで & が出た場合、型付けで失敗していることを示されている

  • 例えば int & float という表示は intかつfloat を意味し、実現できない型
  • なので、その場合は関数定義などを見直すべき
# let f = function `A x -> x+1 | `B -> 2;;
val f : [< `A of int | `B ] -> int = <fun>
# let g = function `A x -> int_of_float x+1 | `B -> 2;;
val g : [< `A of float | `B ] -> int = <fun>

(*
 * 間違った型付け[int & float]が返っている
 * hoge & fuga は実現不可能な型なので、以下の型定義は実質 [< `B] -> int
 *)
# let f_or_g b = if b then f else g;;
val f_or_g : bool -> [< `A of int & float | `B ] -> int = <fun>

使い所

多相ヴァリアントはちゃんと使うには訓練が必要。
多くの場合では通常のヴァリアントで済むはず。

おまけ: 多相レコードはあるのか?

多相ヴァリアントがあるなら多相レコード( { `hoge: int } みたいなもの)もあるのでは?と思ったが、そういうものはない。
代わりにOCaml の object がそれに相当する機能を提供しているもよう。
ただし、 object にはパターンマッチがないためあまり使われないとのこと。

参考

参考文献

プログラミング in OCaml

非常に分かりやすく、OCamlを学ぶ際にオススメです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1