.. 6-detail .. index:: single: osh(1) single: value .. _label6: 6. 表現と値 ================================== omakeは多くのシステムとIO関数を含んでいる、フル機能のプログラミング言語です。この言語はオブジェクト指向言語であり、数値や文字列のような基本の型もすべてオブジェクトで表現されます。しかしながら、このomake言語は3つの点において、他のスクリプト言語とは異なっています。 * 動的なスコーピングを行います。 * IOを除いて、この言語は全体が関数的です。この言語は代入という操作が存在しません。 * 値の評価は通常の場合その場で行われます。これは、式が表れた瞬間に評価されるということを意味しています。 これらの機能を確かめるため、今回私たちは ``osh(1)`` omakeプログラムを使いました。 ``osh(1)`` プログラムは式を入力したら、すぐに結果が出力されるインタープリターとなっています。 ``osh(1)`` は通常シェル上で実行するために、入力された文章をコマンド文として解釈しますので、式を直接評価するためには多くの場合 ``value`` 文を使用します。 :: osh> 1 *** omake error: File -: line 1, characters 0-1 command not found: 1 osh> value 1 - : "1" : Sequence osh> ls -l omake -rwxrwxr-x 1 jyh jyh 1662189 Aug 25 10:24 omake* .. index:: single: 動的なスコーピング single: CC single: CFLAGS single: private .. _label6.1: 6.1 動的なスコーピング ---------------------------------- 動的なスコーピングは言い換えると、変数の値が実行されているスコープ中で、もっとも近くで束縛されている変数によって決定されることを意味しています。以下のプログラムについて考えて見ましょう。 :: OPTIONS = a b c f() = println(OPTIONS = $(OPTIONS)) g() = OPTIONS = d e f f() もし ``f()`` が ``OPTIONS`` 変数を再定義することなく呼び出した場合、この関数は文字列 ``OPTIONS = a b c`` が出力されます。 対照的に、関数 ``g()`` は ``OPTIONS`` 変数を再定義し、 ``f()`` をそのスコープ中で評価しますので、 この関数は文字列 ``OPTIONS = d e f`` が出力されます。 ``g`` の中身はローカルスコープを定義しています。 ``OPTIONS`` 変数の再定義は ``g`` についてローカルであり、この関数が終了した場合、この定義も終了します。 :: osh> g() OPTIONS = d e f osh> f() OPTIONS = a b c 動的なスコーピングはプロジェクトでのコードを簡略化するための非常に有用なツールです。例えば、 ``OMakeroot`` ファイルでは関数の集合、 ``CC`` や ``CFLAGS`` などの変数を使ったプロジェクトのビルドルールについて定義しています。しかしながら、プロジェクト中の異なったパートでは、これらの変数がそれぞれ異なった値であってほしいと思うことがあるでしょう。例えば、サブディレクトリ ``opt`` では、私たちは ``-O3`` オプションを、サブディレクトリ ``debug`` では ``-g`` オプションを用いてビルドしたいものとします。この問題は動的なスコーピングを用いて、関数を再定義することなく、プロジェクト中の一部の変数を置き換えることができます。 :: section CFLAGS = -O3 .SUBDIRS: opt section CFLAGS = -g .SUBDIRS: debug しかしながら、動的なスコーピングは欠点も持っています。はじめに、この機能は分かりずらいです。あなたはプライベートにしたい変数があるとします。しかしこれはどこか別の場所で再定義される恐れがあります。例えば、あなたは以下のサーチパスを組み立てるコードを持っていたとします。 :: PATHSEP = : make-path(dirs) = return $(concat $(PATHSEP), $(dirs)) make-path(/bin /usr/bin /usr/X11R6/bin) - : "/bin:/usr/bin:/usr/X11R6/bin" : String しかしながら、プロジェクトのどこかで ``PATHSEP`` 変数がディレクトリのセパレータ ``/`` で再定義された場合、この関数は突如文字列 ``/bin//usr/bin//usr/X11R6/bin`` を返すようになります。あなたは明らかにそれを望んでいないのにです。 ``private`` ブロックはこの問題を解決するために用いられます。 ``private`` ブロック内で定義された変数は静的なスコーピングを用います。これは、変数の値がソーステキストのスコープ中で、もっとも最近の定義によって決定されることを示しています。 :: private PATHSEP = : make-path(dirs) = return $(concat $(PATHSEP), $(dirs)) PATHSEP = / make-path(/bin /usr/bin /usr/X11R6/bin) - : "/bin:/usr/bin:/usr/X11R6/bin" : String .. _label6.2: 6.2 関数評価 ---------------------------------- IOを除いて、omakeのプログラムは全体が関数的です。これは二つの意味を持っています。 * 代入という操作が存在しません。 * 関数は引数を渡して、別の値を返す「値(value)」です。 二番目についてはそのままの説明です。例えば、以下のプログラムでは関数の値を返すことによって加算する関数を定義しています。 :: incby(n) = g(i) = return $(add $(i), $(n)) return $(g) f = $(incby 5) value $(f 3) - : 8 : Int 一番目については恐らく最初は困惑することでしょう。代入なしで、いったいどのようにして、サブプロジェクトにプロジェクトのグローバルな振る舞いを修正することができるのでしょうか?実際、この省略された説明は意図的にされています。サブプロジェクトが他のプロジェクトの邪魔をしないことを保障されているとき、ビルドスクリプトはより書きやすくなります。 しかしながら、サブプロジェクトが親のオブジェクトに情報を伝える必要がある場合や、内部のスコープが外部のスコープに情報を伝える必要がある場合が存在することも確かです。 .. index:: single: export single: .PHONY .. _label6.3: 6.3 環境のエクスポート ---------------------------------- export文によってすべて、あるいは一部の内部スコープの情報を親スコープに伝えることができます。もし引数が存在しない場合、全体のスコープの情報が親に伝えられます。さもなければ引数の変数のみが伝えられます。もっともよく使うやり方は、条件分岐中のいくつか、あるいはすべての定義をエクスポートする場合です。以下の例では、変数 ``B`` は評価された後に、2に束縛されます。変数 ``A`` は再定義されません。 :: if $(test) A = 1 B = $(add $(A), 1) export B else B = 2 export もし ``export`` 文が引数なしに用いられた場合、以下のすべてが出力されます。 * 動的にスコープされたすべての値 (":ref:`label5.5`"で説明しました) * 現在のワーキングディレクトリ * 現在のUNIX環境 * 現在の暗黙のルールと暗黙の依存関係 (詳細は ":ref:`label8.11.1`" を参照してください) * 現在の"phony"ターゲット宣言の集合 (詳細は ":ref:`label8.10`",":ref:`label8.11.3`" を参照してください) .. index:: single: export .. _label6.3.1: 6.3.1 区域のエクスポート ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *この機能はバージョン0.9.8.5で導入されました。* ``export`` 文はブロックの最後で実行する必要はありません。エクスポートはブロック中のブロックも、ブロックの終わりでエクスポートされます。言い換えると、 ``export`` はその文の次にくるプログラムでも用いられます。これはコード量を減らすという点で特に有用です。以下の例では、変数 ``CFLAGS`` は両方の条件分岐文からエクスポートされます。 :: export CFLAGS if $(equal $(OSTYPE), Win32) CFLAGS += /DWIN32 else CFLAGS += -UWIN32 .. index:: single: export single: return .. _label6.3.2: 6.3.2 エクスポートされた区域から値を返す ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *この機能はバージョン0.9.8.5で導入されました。* ブロックによって返された値は ``export`` を使用してもエクスポートされません。この値は普通に計算された場合、ブロック最後の状態の値として解釈され、エクスポートを無視します。例えば、私たちはマップの文字列をユニークな整数値に改良したテーブルを作りたいと思い、以下のプログラムを考えたとします。 :: # 空のマップ table = $(Map) # テーブルにエントリーを追加 intern(s) = export if $(table.mem $s) table.find($s) else private.i = $(table.length) table = $(table.add $s, $i) value $i intern(foo) intern(boo) intern(moo) # "boo = 1" と出力 println($"boo = $(intern boo)") 文字列 ``s`` が与えられると、関数 ``intern`` は ``s`` に既に関連付けられている値を返し、そうでない場合は新しい値を関連付けます。この場合、このテーブルは新しい値にアップデートされます。関数の初めに ``export`` をつけることによって、 ``table`` 変数はエクスポートされます。一方、 ``s`` や ``i`` に束縛されている値はプライベートなので、エクスポートされません。 omakeでの評価は先行して行われます。これは評価文に遭遇した場合、即座に式の評価が行われることを意味しています。この効果の一つとして、変数が定義されたときに、右側の変数定義が展開されることが挙げられます。 :: osh> A = 1 - : "1" osh> A = $(A)$(A) - : "11" 二番目の定義文の右側 ``A = $(A)$(A)`` は初めに評価され、シーケンス ``11`` が生成されました。変数 ``A`` は新しい値として再定義されます。動的なスコーピングを用いて束縛した場合、これは従来の命令型プログラミングと同じ多くの特性を持ちます。 :: osh> A = 1 - : "1" osh> printA() = println($"A = $A") osh> A = $(A)$(A) - : "11" osh> printA() 11 この例では、出力関数は ``A`` のスコープ中で定義されます。最後の行でこの関数が呼び出されたとき、 ``A`` の動的な値は ``11`` であるので、この値が出力されます。 しかしながら、動的なスコーピングと命令型のプログラミングは混同すべきではありません。以下の例では違いについて示しています。二番目の ``printA`` は ``A = x$(A)$(A)x`` が定義されているスコープには存在していませんので、この関数は元の値 ``1`` を出力します。 :: osh> A = 1 - : "1" osh> printA() = println($"A = $A") osh> section A = x$(A)$(A)x printA() x11x osh> printA() 1 遅延評価式の使用で評価順序を制御する詳細については、":ref:`label7.5`"を参照してください。 .. index:: single: オブジェクト single: extends single: class single: instanceof .. _label6.4: 6.4 オブジェクト ---------------------------------- omakeはオブジェクト指向型言語です。数や文字列のような基本的な値を含む、すべてはオブジェクトで表現されます。多くのプロジェクトの場合、通常のトップレベルのオブジェクト中でほとんどの式が評価されるため、これを見ることはあまりありませんが、 ``Pervasives`` オブジェクトと、2,3個の他のオブジェクトが最初から定義されています。 しかしながら、オブジェクトはデータを構築するための追加手段を提供し、さらにオブジェクトを慎重に使用することで、あなたのプロジェクトをより簡単にしてくれるでしょう。 オブジェクトは以下の文法で定義されます。これは ``name`` をいくつかのメソッドと値を持ったオブジェクトとして定義しています。 :: name. = # += も同じくらいよく使います extends parent-object # なくても構いません class class-name # なくても構いません # プロパティ X = value Y = value # メソッド f(args) = body g(arg) = body ``extends`` 文はこのオブジェクトが指定された ``parent-object`` に継承されていることを指定します。オブジェクトは任意の数の ``extends`` 文を含めることができます。もしおおくの ``extends`` 文が存在した場合、すべての親オブジェクトのメソッドとプロパティは継承されます。もし名前が衝突していた場合、前の定義は後の定義でオーバーライドされます。 ``class`` 文はなくても構いません。もし指定されていた場合、 ``instanceof`` 演算子を使うことでオブジェクト名を新たに定義することができるようになります。これは下で議論する ``::`` スコープ文と同様です。 オブジェクトには任意の内容のプログラムを記述できます。中身で定義された変数はプロパティと定義され、関数はメソッドと定義されます。 .. index:: single: プロパティ single: メソッド .. _label6.5: 6.5 プロパティとメソッドの呼び出し ---------------------------------- オブジェクトのプロパティとメソッドは ``object.name`` 表記を用いて命名されます。例えば、一次元の点の値について定義してみましょう。 :: Point. = class Point # 通常の値 x = $(int 0) # 新しい点を生成 new(x) = x = $(int $(x)) return $(this) # ひとつ進める move() = x = $(add $(x), 1) return $(this) osh> p1 = $(Point.new 15) osh> value $(p1.x) - : 15 : Int osh> p2 = $(p1.move) osh> value $(p2.x) - : 16 : Int ``$(this)`` は常に現在のオブジェクトに置き換える変数です。式 ``$(p1.x)`` はオブジェクト ``p1`` の ``x`` の値を呼び出します。式 ``$(Point.new 1)`` は ``Point`` オブジェクトの ``new`` メソッドを呼び出す式で、初期値として15が保持された新しいオブジェクトを返します。 ``$(p1.move)`` もメソッドの呼び出しで、16が保持された新しいオブジェクトを返します。 オブジェクトは関数的であり、その場で存在しているオブジェクトのプロパティやメソッドを修正することは不可能であることに注意してください。よって、 ``new`` と ``move`` メソッドは新しいオブジェクトを返します。 .. index:: single: オーバーライド .. _label6.6: 6.6 メソッドのオーバーライド ---------------------------------- 1つ移動させる代わりに、2つ移動させるメソッドを持った新しいオブジェクトを作ることについて考えてみましょう。 ``move`` メソッドをオーバーライドすることでこれを実現することができます。 :: Point2. = extends $(Point) # moveメソッドをオーバーライド move() = x = $(add $(x), 2) return $(this) osh> p2 = $(Point2.new 15) osh> p3 = $(p2.move) osh> value $(p3.x) - : 17 : Int しかし、これを行うと古い ``move`` メソッドは完全に置き換わります。 .. _label6.7: 6.7 親の呼び出し ---------------------------------- 新しい ``move`` メソッドを、古い ``old`` メソッドを二回呼び出すことで定義したい場合について考えてみましょう。これは表記 ``$(classname::name )`` を用いることで親を呼び出すことができます。 ``classname`` は親クラスの名前で、 ``name`` のメソッドやプロパティが関連付けられている必要があります。それでは、 ``Point2`` オブジェクトを別の方法で定義してみましょう。 :: Point2. = extends $(Point) # 古いメソッドを2回呼び出す move() = this = $(Point::move) return $(Point::move) 最初の ``$(Point::move)`` の呼び出しは現在のオブジェクト( ``this`` 変数)を再定義していることに注意してください。なぜならこのメソッドは新しいオブジェクトを返し、二回目の呼び出しで再利用されるからです。