OCamlの概要:データ型とマッピング[3]

[約。 レーン:継続的な翻訳。 第一部第二部 ]

リンクリスト


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 ++には、 structstruct略)の概念があります。 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は次の警告を発行しました。
警告:このパターンマッチングは完全ではありません。
一致しない値の例を次に示します。
製品_

Source: https://habr.com/ru/post/J108920/


All Articles