[約。 レーン:継続的な翻訳。
第一部 、
第二部 ]
リンクリスト
OCamlは、Perlと同様に、言語レベルの組み込みリストをサポートしています。 すべてのリスト項目は同じタイプでなければなりません。 タイプを決定するために、式が使用されます:
[1; 2; 3]
注:コンマではなくセミコロン。
[]
は空のリストを意味します。
このリストには、
先頭 (最初の要素)と
末尾 (先頭以外の要素)があります。 頭は要素です。 尾はリストです。 上記の例では、headは整数1で、tailはリスト
[2; 3]
[2; 3]
。
書き込みの代替形式は、
head :: tail
という形式で
構築演算子(cons)を使用することです。 以下の行は互いに完全に同等です。
[1; 2; 3]
1 :: [2; 3]
1 :: 2 :: [3]
1 :: 2 :: 3 :: []
なぜ建設オペレーターに言及したのですか? リストのパターンマッチングを開始するときに役立ちます。これについては後で説明します。
リンクリストのデータ型
整数のリンクリストのデータ型は
int list
です。 タイプ
foo
のオブジェクトのリンクリストの一般的なタイプは
foo list
です。
これは、すべてのリスト項目が同じタイプでなければならないことを意味します。 しかし、ポリモーフィックリスト(たとえば、
'a list
)を使用できます。これは、「何かのリスト」で機能する汎用関数を作成するときに非常に便利です。 (注、
'a list
は
'a list
の要素が異なるタイプであることを意味するものではありません。たとえば、整数と文字列の混合から成るリストを作成することはできません。この形式の記述は、同じタイプ ")。
良い例は、
List
モジュールで定義された
length
関数です。 リストに整数、文字列、オブジェクト、または小さな毛皮の動物が含まれているかどうかは関係ありません
List.length
関数はどのタイプのリストにも使用できます。 したがって、
List.lenght
入力し
List.lenght
。
List.length: 'リスト-> int
構造
CおよびC ++には、
struct
(
struct
略)の概念があります。 Javaには同様の方法で使用できるクラスがありますが、それらを使用するにはより骨の折れる作業が必要です。
Cの単純な構造を見てください。
struct pair_of_ints {
int a、b;
};
OCamlで最も単純なものは、
(3,4)
などの
int * int
型の
タプル (
タプル )です。 リストとは異なり、タプルにはさまざまな型の要素を含めることができるため、たとえば(3、“ helo”、 'x')は
int * string * char
型です。
Cの構造のやや複雑な実装は、レコードを使用することです。 C構造体のようなレコードでは、名前付き要素を使用できます。 タプルには名前付き要素はありませんが、代わりに、タプルに要素が現れる順序を格納します。 これは、レコードを使用するC構造体と同等です。
タイプpair_of_ints = {a:int; b:int} ;;
上記の行はタイプを定義し、以下ではこのタイプのオブジェクトを実際に
作成します。
{a = 3; b = 5}
型定義で「:」を使用し、指定された型のオブジェクトを作成するときに「=」を使用したことに注意してください。
以下にトップレベルの例を示します(トップレベル):
#type pair_of_ints = {a:int; b:int} ;;
タイプpair_of_ints = {a:int; b:int; }
#{a = 3; b = 5} ;;
-:pair_of_ints = {a = 3; b = 5}
#{a = 3} ;;
一部のレコードフィールドラベルは未定義です:b
OCamlは、エントリを未定義のままにすることを禁止しています。
オプション(資格のある関連付けと転送)
「修飾ユニオン」オブジェクトタイプはCには存在しませんが、gccにはいくつかのサポートがあります。 修飾C結合が実装される頻度の例を次に示します。
struct foo {
int型;
#define TYPE_INT 1
#define TYPE_PAIR_OF_INTS 2
#define TYPE_STRING 3
ユニオン{
int i; // type == TYPE_INTの場合。
intペア[2]; // type == TYPE_PAIR_OF_INTSの場合。
char * str; // type == TYPE_STRINGの場合。
} u;
};
ご覧のとおり、これはあまり快適な光景ではありません。 そして一般的に、それはあまり安全ではありません。プログラマーは、構造体に文字列が含まれる瞬間に
ui
値を使用できます。 そして、ここでのコンパイラは、すべての可能な値がswitch式内でチェックされたことを確認するのに役立ちません(具体的には、この問題は
enum
を使用して部分的に解決されます)。 プログラマは
type
フィールドの値を設定するのを忘れるかもしれません。これはあらゆる種類の楽しみやバグのあるゲームにつながります。 はい、これはすべて面倒です。
OCamlのエレガントでコンパクトなバージョンを次に示します。
タイプfoo = Nothing | intのint | int * intのペア| 文字列の文字列;;
これは型定義です。 各プロットの最初の部分は制限されています| コンストラクターと呼ばれます。 好きな名前を付けることができますが、大文字で始める必要があります。 コンストラクターを使用して値を決定できる場合、その後
of part
が続きます。typeは常に小文字で始まります。 上記の例では、
Nothing
は定数であり、他のすべてのコンストラクターは値とともに使用されます。
実際の
作成では、次のように記述されます。
なし
Int 3
ペア(4、5)
ストリング「hello」
&c。
上記の例の各式は
foo
型です。
注:
of
型の定義に使用されますが、特定の型の要素の作成
of
使用されません。
続きます。 単純なCリストは次のように定義されます
列挙記号{正、ゼロ、負};
また、次のようにOCamlで作成することもできます。
タイプ記号=正| ゼロ| 負の;;
再帰オプション(ツリー用)
オプションは再帰的です。 ほとんどの場合、これはツリー構造を定義するために使用されます。 これは、関数型言語の表現力が見えるようになる場所です。
タイプbinary_tree = intのリーフ| binary_tree * binary_treeのツリー;;
以下にバイナリツリーを示します。 練習のために、それらを紙に描いてみてください:
葉3
ツリー(リーフ3、リーフ4)
ツリー(ツリー(リーフ3、リーフ4)、リーフ5)
ツリー(ツリー(リーフ3、リーフ4)、ツリー(ツリー(リーフ3、リーフ4)、リーフ5))
パラメータ化されたオプション
上記の例のバイナリツリーには、各シートに整数が格納されていました。 しかし
、バイナリツリーの
形状を記述したい場合、後でリーフに保存するものの仕様を残しますか? これは、次のようなパラメーター化された(多態的な)オプションを使用して行うことができます。
タイプ「a binary_tree = Leaf of」a | 'a binary_tree *'のツリーbinary_tree ;;
これは一般的なタイプです。 各シートに整数を格納する正確な型は、
int binary_tree
と呼ばれます。 同様に、各シートに文字列を格納する正確なタイプは、
string binary_tree
と呼ばれ
string binary_tree
。 次の例では、最上位のいくつかのインスタンスのタイプを決定し、タイプ推論システムにそれらのタイプを表示させます。
#リーフ "hello" ;;
-:string binary_tree = Leaf "hello"
#リーフ3.0 ;;
-:float binary_tree = Leaf 3。
タイプ名は逆の順序で書き込まれることに注意してください。 これをリストのタイプ名(たとえば、
int list
比較します。
実際、
a' list
も同じ「逆順」で書かれていることは偶然ではありません。 リストは、この(わずかに奇妙な)定義を持つパラメーター化されたオプションです。
タイプ 'a list = [] | :: 'a *'リスト(*これは実際のOCamlではありません*コード)
これは完全な定義ではありませんが。 より正確な定義は次のとおりです。
#type 'a list = Nil | :: 'a *'リスト;;
タイプ 'a list = Nil | ::「a *」リスト
#なし;;
-: 'a list = Nil
#1 ::なし;;
-:int list = ::(1、Nil)
#1 :: 2 :: Nil ;;
-:int list = ::(1、::(2、Nil))
先ほど言ったことを思い出してください-リストは2つの方法で書くことができます:少量の構文糖
[1; 2; 3]
[1; 2; 3]
[1; 2; 3]
またはより正式には、
1 :: 2 :: 3 :: []
。 上記
a' list
定義を見ると、正式なエントリの説明が表示されます。
リスト、構造、オプション-結果
OCamlの名前 | タイプ定義の例 | 使用例 |
---|
リスト | intリスト | [1; 2; 3] |
タプル | int *文字列 | (3、「こんにちは」) |
記録する | タイプペア= {a:int; b:文字列} | {a = 3; b = "hello"} |
異形 | タイプfoo = intのint | int *文字列のペア | Int 3 |
異形 | タイプ記号=正| ゼロ | 負 | ポジティブ ゼロ |
パラメータ化された 異形 | タイプ 'a my_list =空 | 'a *'の短所my_list | 短所(1、短所(2、空)) |
パターンマッチング(データ型用)
関数型プログラミング言語の本当にクールな機能の1つは、構造を解析し、データのパターンマッチングを行う機能です。 これはまったく「機能的な」機能ではありません。Cのバリエーションの類似点を想像できます。これにより、これを行うことができますが、いずれにしても、これはクールな機能です。
実際のプログラミングタスクから始めましょう。n*(x + y)などの数式用のライティングツールを用意し、上記の式からn * x + n * yを取得するためにシンボリック乗算を実行します。
これらの式のタイプを定義しましょう:
タイプexpr =プラスexpr * expr(*はa + b *を意味します)
| expr * exprのマイナス(*はa-b *を意味します)
| expr * exprの時間(*はa * b *を意味します)
| expr * exprの除算(*はa / bを意味します*)
| 文字列の値(* "x"、 "y"、 "n"など。*)
;;
n *(x + y)という形式の式は、次のように記述できます。
時間(値 "n"、プラス(値 "x"、値 "y"))
ここで、
n * (x+y)
似たものとして
(Value "n", Plus (Value "x", Value "y"))
を出力する関数を書きましょう。 これを行うには、2つの関数を作成します。1つは式を美しい文字列に変換し、もう1つはそれを表示します(分離の理由は、ファイルに同じ行を書き込む必要があり、このために関数全体を繰り返したくないためです)。
let rec_string e =
eと一致
プラス(左、右)-> "(" ^(to_string left)^ "+" ^(to_string right)^ ")"
| マイナス(左、右)-> "(" ^(to_string left)^ "-" ^(to_string right)^ ")"
| 時間(左、右)-> "(" ^(to_string left)^ "*" ^(to_string right)^ ")"
| 分割(左、右)-> "(" ^(to_string left)^ "/" ^(to_string right)^ ")"
| 値v-> v
;;
let print_expr e =
print_endline(to_string e);;
(注:文字列を連結するために
^
演算子が使用されます)
しかし、作業中の画面出力機能:
#print_expr(Times(値 "n"、Plus(値 "x"、値 "y")));;
(n *(x + y))
パターンマッチングの一般的な形式:
オブジェクトを一致させる
パターン->結果
| パターン->結果
...
パターンマッチングの左側は、単純なもの(上記の例の
to_string
の場合のように)でも、アタッチメント付きの複雑なもの
to_string
。 次の例は、
n * (x + y)
形式または
(x + y) * n
形式の式を乗算するための関数です。 このために、ネストされたサンプルを使用します。
let recマルチプルアウトe =
eと一致
時間(e1、プラス(e2、e3))->
プラス(時間(multiply_out e1、multiply_out e2)、
時間(multiply_out e1、multiply_out e3))
| 時間(プラス(e1、e2)、e3)->
プラス(時間(multiply_out e1、multiply_out e3)、
時間(multiply_out e2、multiply_out e3))
| プラス(左、右)->プラス(左にmultiply_out、右にmultiply_out)
| マイナス(左、右)->マイナス(左にmultiply_out、右にmultiply_out)
| 時間(左、右)->時間(左に乗算_アウト、右に乗算_アウト)
| 分割(左、右)->分割(multiply_out左、multiply_out右)
| 値v->値v
;;
ここに作業中です:
#print_expr(multiply_out(Times(値 "n"、Plus(値 "x"、値 "y")));;
((n * x)+(n * y))
マルチプルアウトはどのように機能しますか? 重要な点は、最初の2つのサンプルです。 最初のサンプルは
Time (e1, Plus (e2, e3))
、
e1 * (e2 + e3)
形式の式を比較します。 サンプルとの最初の比較の右側を見て、それが同等であることを確認してください
(e1 * e2) + (e1 * e3)
。
2番目のパターンは、
(e1 + e2) * e3
という形式の式の使用を除いて同じことを行います。
残りのサンプルは式を変更しませんが、本質的に、サブ式で
multiply_out
を再帰的に呼び出します。 これにより、すべての部分式が乗算されます(式の最上位レベルのみを乗算する場合は、残りのすべてのパターンを単純なルール
e -> e
置き換える必要があります)。
反対のことはできますか? (部分式の一般的な部分の因数分解?)もちろんです! (ただし、これは少し複雑です)。 以下のバージョンは、最上位レベルの式でのみ機能します。 もちろん、すべてのレベルの表現(およびより複雑な場合)に拡張できます。
因数分解e =
eと一致
Plus(Times(e1、e2)、Times(e3、e4))e1 = e3-> Times(e1、Plus(e2、e4))の場合
| Plus(Times(e1、e2)、Times(e3、e4))e2 = e4-> Times(Plus(e1、e3)、e4)
| e-> e
;;
#factorize(Plus(Times(値 "n"、値 "x")、Times(値 "n"、値 "y"));;
-:expr = Times(値 "n"、プラス(値 "x"、値 "y"))
因数分解関数は、言語のさらにいくつかの機能を示しています。 各パターンマッチに
カストディアンを追加できます。 キーパーは、試合に続く条件です。 パターンマッチングは、パターンマッチと
when
の条件
when
満たされている場合にのみ機能します。
オブジェクトを一致させる
パターン[when条件]->結果
パターン[when条件]->結果
...
この言語の2番目の機能は
=
演算子で、式間の「構造的対応」をチェックします。 これは、各式に再帰的に入り、すべてのレベルでそれに応じてチェックすることを意味します。
OCamlは、コンパイル時に、テンプレートで考えられるすべてのケースをカバーしたことを確認できます。
Product
バリアントを追加して
expr
タイプの定義を変更しました。
タイプexpr =プラスexpr * expr(*はa + b *を意味します)
| expr * exprのマイナス(*はa-b *を意味します)
| expr * exprの時間(*はa * b *を意味します)
| expr * exprの除算(*はa / bを意味します*)
| exprリストの積(*はa * b * c * ... *を意味します)
| 文字列の値(* "x"、 "y"、 "n"など。*)
;;
次に、to_string関数を追加せずにコピーしようとしました。 OCamlは次の警告を発行しました。
警告:このパターンマッチングは完全ではありません。
一致しない値の例を次に示します。
製品_