OCamlの概要:OCamlでのプログラムの構造[2]

[約。 翻訳:継続的な翻訳、最初の記事はこちら ]

OCamlのグラフィックを含む記事のティーザー

OCamlのプログラム構造



ここで、OCaml上の実際のプログラムの高レベル分析に少し時間を費やします。 ローカルとグローバルの定義、使用の違いを示したいと思います;;



および;



、モジュール、ネストされた関数、リンク。 このため、OCamlの多くの概念に出くわします。OCamlを学習することは初心者には意味がありません。 それらに焦点を合わせるのではなく、プログラムの形式と私が指し示す言語の機能の一般的な考え方に焦点を当てます。



ローカル「変数」(実際にはローカル式)



Cのaverage



関数を使用して、ローカル変数を追加します(前の章の例と比較してください)。



二重平均(二重a、二重b)
 {
  二重和= a + b;
   return sum / 2;
 }


OCamlでこれを見てください:



平均ab =
  合計= a +とします。  b in
  合計/。  2.0 ;;


標準の式let name = expression in



使用してローカルの名前付き式を定義し、後で式の代わりにname



を使用できます;;



、これはローカルコードブ​​ロックの終わりを意味します。 in宣言の後にインデントすらしていないことに注意してください。 let ... in



を1つのステートメントであるかのように取り込んでください。



さて、Cのローカル変数を上記のローカルの名前付き式と比較するのは手間がかかります。 それらは多少異なりますが。 Cコードの変数sum



は、スタック上のメモリを使用します。 さらに、任意の値をsumに割り当てたり、値が保存されているメモリアドレスを取得したりすることもできます。 OCamlバージョンの場合、これはそうではありません。 その中で、 sum



は式a +. b



単なる短縮名ですa +. b



a +. b



sum



何かに割り当てる方法はありません。 (少し後で、値を変更できる変数を作成する方法を示します)。



最後に説明する別の例を示します。 これらの2つのコードスニペットは、同じ値((a + b)+(a + b) 2 )を返す必要があります。



 let fab =
   (a +。b)+。  (a +。b)** 2。
   ;;


 let fab =
   x = a +としましょう。  b in
   x +。  x ** 2。
   ;;


理論的には2番目のバージョンの方が速くなります(ただし、ほとんどのコンパイラーは「共通部分式の破棄」と呼ばれる段階を実行できるため)、間違いなく読みやすくなります。 2番目の例のx



は、 a +. b



短い名前ですa +. b



a +. b





グローバルな「変数」(実際にはグローバルな式)



また、トップレベルでさまざまなもののグローバル名を定義できます。上記のローカル「変数」のように、それらは実際には変数ではなく、さまざまなものの短い名前です。 次に、実際の例を示します(わずかにトリミングされていますが)。



 let html =
   let content = read_whole_file file in
   GHtml.html_from_stringコンテンツ
   ;;

 let menu_bold()=
  一致するbold_button#アクティブ
     true-> html#set_font_style〜有効:[`BOLD]()
   |  false-> html#set_font_style〜無効化:[`BOLD]()
   ;;

 let main()=
   (*省略コード*)
   factory#add_item "Cut"〜キー:_X〜コールバック:html#カット
   ;;




これは実際のコードです。 html-式let html =



によってプログラムの先頭に作成されたHTML編集ウィジェット(lablgtkライブラリオブジェクト)。 その後、さらにいくつかの機能で使用されます。



上記のコードスニペットのhtml



名は、Cまたは他の命令型言語のように、実際のグローバル変数として使用しないでください。 「 html



ポインタ」を「保存」するためのメモリ割り当てはありません。 他のウィジェットへのポインタとして再定義するなど、 html



何かを割り当てることは不可能です。 次のセクションでは、実際の変数である参照について説明します。



束縛



let ...



使用は、トップレベル(グローバル)または内部関数(ローカル)のどこに関係なく、しばしばlet-binding(letを使用したバインド)と呼ばれます。



参照:実変数



しかし、プログラムの実行中に値を割り当てたり変更したりできる実際の変数が必要な場合はどうでしょうか。 この場合、 リンクを使用する必要があります。 リンクは、C / C ++のポインターに非常に似ています。 Javaでは、オブジェクトを格納するすべての変数は、実際にはオブジェクトへの参照(ポインター)です。 パールでは、リンクはOCamlのようにリンクです。



OCamlでint



リンクを作成する方法は次のとおりです。



 ref 0 ;;


実際、このような式はあまり有用ではありません。 リンクを作成しましたが、彼女に名前を付けなかったため、ガベージコレクターがすぐに来てそれを収集しました! (実際、ほとんどの場合、コンパイル段階で破棄されます)。 リンクに名前を付けましょう:



 let my_ref = ref 0 ;;


現在、このリンクにはゼロがすべて格納されています。 ここに別の値を書き込みましょう(割り当て操作):



 my_ref:= 100 ;;


そして、リンクが今何を保存しているか見てみましょう:



 #!my_ref ;;
 -:int = 100


したがって、演算子:=



リンクの割り当てに使用され、演算子!



リンクを逆参照し、コンテンツを返します。 以下は、C / C ++との簡単で効果的な比較です。

OCaml C / C ++
 let my_ref = ref 0 ;;
 my_ref:= 100 ;;  
 !my_ref


 int a = 0;  int * my_ptr =&a;
 * my_ptr = 100;
 * my_ptr




リンクには独自のスコープがありますが、あまり頻繁に使用することはありません。 より一般的なのは、 let name=expression in



を使用して関数内のローカル式に名前を付けることです。

入れ子関数



Cでは、ネストされた関数の概念はありません。 GCCはCプログラムのネストされた関数をサポートしていますが、この拡張機能を使用する単一のプログラムは知りません。 とにかく、これはgcc情報ページがネストされた関数について書いているものです:

「入れ子関数」は、別の関数内で定義された関数です(入れ子関数はGNU C ++ではサポートされていません)。 ネストされた関数の名前は、それらが定義されたブロックに対してローカルです。 たとえば、ネストされた関数 'square'の定義は次のとおりです。これは2回呼び出されます。



 foo(ダブルa、ダブルb)
 {
   double square(double z){return z * z;  }

   return square(a)+ square(b);
 }


ネストされた関数は、関数が定義されたときに表示される外部関数のすべての関数にアクセスできます。 これは、いわゆる「字句可視性の領域」です。 次に、オフセットと呼ばれる変数を継承するネストされた関数の例を示します。



 bar(int *配列、intオフセット、intサイズ)
 {
   intアクセス(int *配列、intインデックス)
     {配列[インデックス+オフセット]を返す;  }
   int i;
   / * ... * /
   for(i = 0; i <サイズ; i ++)
     / * ... * /アクセス(配列、i)/ * ... * /
 }




アイデアが明確であることを願っています。 ただし、入れ子関数は非常に便利で、OCamlで積極的に使用されています。 以下は、実際のコードからネストされた関数を使用する例です。



 read_whole_channel chan =
   let buf = Buffer.create 4096 in
   let rec loop()=
     let newline = input_line chan in
     Buffer.add_string buf newline;
     Buffer.add_char buf '\ n';
    ループ()
  で
  試してみる
    ループ()
  と
     End_of_file-> Buffer.contents buf ;;


心配しないでください。コード全体を理解していなくても、まだ説明していない多くの概念が含まれています。 代わりに、タイプunit



引数を取る中央のネストされたloop



関数に注目してloop



。 read_whole_channel関数からloop ()



を呼び出すことができますが、関数の外部で定義されていません。 ネストされた関数は、メイン関数で定義された変数にアクセスできます(この方法で、 loop



はローカル変数buf



およびchan



アクセスできます)。



ネストされた関数を定義するための形式は、ローカルの名前付き式のタスクに似ています: let name arguments = function-defenition in.







通常、関数の定義は新しい行でインデントされます(上記の例のように)。 また、関数が再帰的である場合(上記の例のように) let



代わりにlet rec



を使用する必要があることを忘れないでください。



モジュールとopen



コマンド



多くの興味深いモジュール(有用なコードを含むライブラリ)がOCamlで提供されます。 たとえば、グラフの描画、一連のウィジェットを使用したGUIインターフェイスの作成、多数のデータ構造の処理、POSIXシステムコールの作成のための標準ライブラリがあります。 これらのライブラリは、/ usr / lib / ocaml / VERSION(Unixの場合)にあります。 この例では、 Graphics



と呼ばれるかなり単純なモジュールに焦点を当てます。



Graphics



モジュールは5つのファイルで構成されています(私のシステム上):



 /usr/lib/ocaml/3.08/graphics.a
 /usr/lib/ocaml/3.08/graphics.cma
 /usr/lib/ocaml/3.08/graphics.cmi
 /usr/lib/ocaml/3.08/graphics.cmxa
 /usr/lib/ocaml/3.08/graphics.mli


[約。 transl .:しかし、私のシステム(Debian Sid)では、モジュールはバージョンを指定せずに/ usr / lib / ocamlに直接ダンプされます]。



まず、 graphics.mli



ファイルに注目しましょう。 これはテキストファイルであるため、その内容を簡単に確認できます。 まず、名前がgraphics.mli



ではなくgraphics.mli



であることに注意してください。 OCamlは、モジュールに関しては常にファイル名の最初の文字を大文字にします。 事前に知らない限り、これは非常に混乱する可能性があります。



Graphics



で関数を使用する場合、2つの方法があります。 または、プログラムの開始時に、 open Graphics;;



宣言を記述しopen Graphics;;



または、対応する関数へのすべての呼び出しを接頭辞Graphics.open_graph.open



補完します。 open



はJavaのimport



関数に少し似ており、Pearlのuse



式に少し似ています(似ています)。



[Windowsユーザーの場合:この例をWindowsで対話的に機能させるには、別のトップレベル(トップレベル)を作成する必要があります。 次のようなコマンドラインからコマンドを実行します:ocamlmktop -o ocaml-graphics graphics。]



次のいくつかの例ですべてを明確にする必要があります(これら2つの例は異なることを示しています-両方とも試してください)。 最初の例ではopen_graph



呼び出し、2番目の例ではGraphics.open_graph



呼び出しています。 [約。 trans .:記事の冒頭に、プログラムの機能のスクリーンショットがあります]。



 (*この例をコンパイルするには:ocamlc graphics.cma grtest1.ml -o grtest1 *)

グラフィックを開く;;

 open_graph "640x480" ;;
 for i = 12 downto 1 do
   let radius = i * 20 in
   set_color(if(i mod 2)= 0 then red else yellow);
   fill_circle 320 240 radius
完了;;
 read_line();;







 (*この例をコンパイルするには:ocamlc graphics.cma grtest2.ml -o grtest2 *)

 Random.self_init();;
 Graphics.open_graph "640x480" ;;

繰り返してみましょうr x_init i =
         i = 1の場合、x_init
        他に
                 let x =反復r x_init(i-1)in
                 r *。  x *。  (1.0-。X);;

 x = 0〜639の場合
         r = 4.0 *とします。  (float_of_int x)/。  640.0インチ
         i = 0〜39の場合
                 let x_init = Random.float 1.0 in
                 let x_final = it r r x_init 500 in
                 let y = int_of_float(x_final *。480.)in
                 Graphics.plot xy
        やった
完了;;

 read_line();;


どちらの例も、まだ話していない言語の機能をいくつか使用しています。ループの必須、 if-then-else



ブロック、再帰です。 これについては後で説明します。 それにもかかわらず、あなたはまだ:(1)それらがどのように機能するかを理解しようとする(2)どのように型推論がエラーをキャッチするのを可能にするか

Pervasives



モジュール



「開く」必要のないモジュールが1つあります。 これはPervasives



モジュールです(/usr/lib/ocaml/3.08/pervasives.mli [approx。Transl。:I have /usr/lib/ocaml/pervasives.mli])。 Pervasives



モジュールのすべての文字は、すべてのOCamlプログラムに自動的にインポートされます。

モジュールの名前を変更する



Graphics



文字を使用したいが、すべてをインポートしたくなく、グラフィックスを毎回印刷するのが面倒な場合はどうでしょうか? この手法を使用してモジュールの名前を変更するだけです:



モジュールGr =グラフィック;;

 Gr.open_graph "640x480" ;;
 Gr.fill_circle 320 240 240 ;;
 read_line();;


実際、この手法は、ネストされたモジュールをインポートする場合(モジュールを別のモジュールにネストすることができます)に非常に便利ですが、ネストされたモジュールへのパスを毎回印刷する必要はありません。

使用するとき、およびスキップするとき;;



および;





ここで、非常に重要な問題を検討します。 使用する場合;;



いつ使用するか 、両方のオプションをいつスキップする必要がありますか? このトリッキーな質問は、よく理解するまで残ります。 彼は長い間、著者とOCamlを勉強しながら心配していた。



ルール番号1-使用する必要があります;;



コードの最上位(最上位)でステートメントを分離し、関数定義または他の種類のステートメントの内部には決して入れない



グラフィックのある2番目の例からのフラグメントを見てみましょう。



 Random.self_init();;
 Graphics.open_graph "640x480" ;;

繰り返してみましょうr x_init i =
         i = 1の場合、x_init
        他に
                 let x =反復r x_init(i-1)in
                 r *。  x *。  (1.0-。X);;




最上位には2つのステートメントがあり、 iterate



関数の定義があります。 それらのそれぞれが終了します;;







ルール番号2- 時々スキップできます;;



。 新入生として、あなたは特にこの規則について考えて、常に書くべきではありません;;



規則1で規定されているとおり。 しかし、誰か他の人のコードを読むと、時々不足することがあります;;



。 省略する場所;;







以下にコードの例を示します;;



可能な限り省略:



ランダムに開く(* ;; *)
グラフィックを開く;;

 self_init();;
 open_graph "640x480"(* ;; *)

繰り返してみましょうr x_init i =
         i = 1の場合、x_init
        他に
                 let x =反復r x_init(i-1)in
                 r *。  x *。  (1.0-。X);;

 x = 0〜639の場合
         r = 4.0 *とします。  (float_of_int x)/。  640.0インチ
         i = 0〜39の場合
                 let x_init = Random.float 1.0 in
                 let x_final = it r r x_init 500 in
                 let y = int_of_float(x_final *。480.)in
                 Graphics.plot xy
        やった
完了;;

 read_line()(* ;; *)


規則3と4が適用され;



。 これらは、 ;;



のルールとはまったく異なります;;



。 単一のセミコロン(;)は、シーケンスポイントと呼ばれます [約。 transl .:シーケンスポイントの翻訳と間違えられる可能性があります]。これは、C、C ++、Java、またはPearlの単一のセミコロンとまったく同じ役割を果たします。 これは、「この場所の前にすべてを行うと、この場所の後にすべてを行う」という意味です。 あなたはそれを知りませんでした。



ルール番号3:ステートメントでlet ... in



検討let ... in



、決してベットしない;



彼の後。



ルール番号4:コードブロック内の他のすべてのステートメントを完了します;



最後を除いて



上記の内側のforループは良い例です。 一度も使用したことがないことに注意してください;



コードで。



         i = 0〜39の場合
                 let x_init = Random.float 1.0 in
                 let x_final = it r r x_init 500 in
                 let y = int_of_float(x_final *。480.)in
                 Graphics.plot xy
        やった


使用可能な唯一の場所;



-これはGraphics.plot xy



の行ですが、これはブロックの最後の行であるため、ルールNo. 4に従って、配置する必要はありません。



「;」に関する注意



ブライアンハートは私を修正しました:

;



-たとえば、加算演算子( +



)と同じ演算子。 まあ、まったく似ていないが、実際には-まさにそのようだ。 +はint -> int -> int



型ですint -> int -> int



つの整数を取り、整数(それらの合計)を返します。 ;



タイプunit -> 'b -> 'b



持ちます-2つの概念を取り、単に2番目の概念を返します。 Cのコンマとは異なります。 a;b;c;d



a + b + c + d



同じくらい簡単に記述できます。



これはそれらの基本概念の1つであり、その理解は言語を理解しますが、実際に大声で話されることはありません-OCamlでは文字通りすべてが表現です。 if/then/else



は式です。 a;b



は式です。 match foo with ...



は式です。 以下のコードは完全に正しいです(そしてそれらはすべて同じことをします):



  let fxby = if b then x + y else x + 0
 
  let fxby = x +(bならばyでなければ0)
 
  let fxby = x +(bをtrue-> y | false-> 0にマッチ)
 
  let fxby = x +(let gz = function true-> z | false-> 0 in gyb)
 
  let fxby = x +(let _ = y + 3 in(); if b then y else 0)


後者をよく見てください-私は使用してい;



「2つのステートメントを結合するための演算子として。 OCamlのすべての関数は次のように表現できます。

  let name [parameters] = expression


OCamlでの「表現」の定義は、Cでの表現よりも幾分広範です。 実際、Cには「ステートメント」という概念がありますが、CのすべてのステートメントはOCamlの単なる表現;



(組み合わせた;



)。



の唯一の違い;



および+



は、 +



を関数として参照する機能です。 たとえば、整数リストを合計するsum_list



関数をsum_list



ように定義できます。

 let sum_list = List.fold_left(+)0






すべて一緒に:実際のコード



このセクションでは、labgtk 1.2ライブラリの実際のコードの一部を示します(Labgtkは、ネイティブUnixウィジェット用のOCamlのインターフェイスです)。 警告:このコードにはまだ話していないことがたくさんあります。 詳細に立ち入るのではなく、コードの一般的な構造、作者が使用した場所を見てください;;



彼らが使用した場所;



open



使用場所、テキストの打ち方、ローカルおよびグローバル表現の使用方法。



...しかし、まったく迷子にならないように、いくつかのヒントを紹介します。







最初のフラグメント:プログラマーは標準ライブラリのバンドルを開きます(次のキーワードが開いているかletであるため、 ;;



省略します)。 また、file_dialogという関数も作成します。 この関数内で、2行のステートメントlet sel = .. in



を使用して、 sel



という名前の式を定義let sel = .. in



ます。 その後、彼はselに関するいくつかのメソッドを呼び出します。



 (*最初のスニペット*)
標準ラベルを開く
 GMainを開く

 let file_dialog〜title〜callback?filename()=
   sel =
     GWindow.file_selection〜title〜modal:true?ファイル名()in
   sel#cancel_button#connect#クリック〜コールバック:sel#destroy;
   sel#ok_button#connect#クリック〜コールバック:do_ok;
   sel#show()


2番目のフラグメント:トップレベルのグローバル名の大きなリスト。 著者がすべて省略したことに注意してください;;



ルール2に従って。



 (* 2番目のスニペット*)

 let window = GWindow.window〜幅:500〜高さ:300〜タイトル: "editor"()
 let vbox = GPack.vbox〜packing:window#add()

 let menubar = GMenu.menu_bar〜パッキング:vbox#pack()
 let factory =新しいGMenu.factoryメニューバー
 let accel_group = factory#accel_group
 let file_menu = factory#add_submenu "File"
 let edit_menu = factory#add_submenu "編集"

 let hbox = GPack.hbox〜パッキング:vbox#add()
 let editor = new editor〜パッキング:hbox#add()
 let scrollbar = GRange.scrollbar `VERTICAL〜パッキング:hbox#pack()


3番目のフラグメント:作成者は、 GdkKesyms



モジュールからすべてのシンボルをGdkKesyms



ます。 次に、異常なlet-binding



ます。 let _ = expression



は、「 let _ = expression



の値(すべての副作用の実行を含む)を計算しますが、結果を破棄する」ことを意味します。 この場合、「式の値を計算する」とは、メインGTKサイクルであるMain.main ()



実行を意味し、その副作用は画面上のウィンドウの表示とアプリケーション全体の実行です。 Main.main ()



の呼び出しの「結果」は重要でMain.main ()



。 ほとんどの場合、これはunit



ですが、私はチェックしませんでした-確かに、アプリケーションが完了するまで戻りません。



このフラグメントには、実際には手続き型コマンドの長いチェーンが含まれていることに注意してください。 これは本当の古典的な命令型プログラムです。



 (* 3番目のスニペット*)

 GdkKeysymsを開きます

 let _ =
   window#connect#destroy〜コールバック:Main.quit;
   let factory = new GMenu.factory file_menu〜accel_group in
   factory#add_item "Open ..."〜キー:_O〜コールバック:editor#open_file;
   factory#add_item "Save"〜キー:_S〜コールバック:editor#save_file;
   factory#add_item "Save as ..."〜コールバック:エディター#save_dialog;
   factory#add_separator();
   factory#add_item "Quit"〜キー:_Q〜コールバック:window#destroy;
   let factory = new GMenu.factory edit_menu〜accel_group in
  ファクトリー#add_item "コピー"〜キー:_C〜コールバック:エディター#テキスト#copy_clipboard;
   factory#add_item "Cut"〜キー:_X〜コールバック:エディター#テキスト#cut_clipboard;
   factory#add_item "Paste"〜キー:_V〜コールバック:エディター#text#paste_clipboard;
   factory#add_separator();
   factory#add_check_item "Word wrap"〜アクティブ:false
     〜コールバック:エディター#テキスト#set_word_wrap;
   factory#add_check_item "Read only"〜active:false
     〜コールバック:(fun b-> editor#text#set_editable(not b));
  ウィンドウ#add_accel_group accel_group;
  エディター#テキスト#イベント#接続#button_press
     〜コールバック:(fun ev->
       let button = GdkEvent.Button.button ev in
      ボタン= 3の場合、開始
         file_menu#ポップアップ〜ボタン〜時間:(GdkEvent.Button.time ev); 本当
      終了する場合はfalse);
  エディター#テキスト#set_vadjustmentスクロールバー#調整;
  ウィンドウ#show();
   Main.main()




[約。 trans .:誰かがエラーやrekryakyを見つけた場合、書き込み、修正]



All Articles