目次へ    最終更新 : 2003/1/19

第3章 関数と定数の定義





 本章では、関数(実際は、グラフ書換規則)と定数(実際は、グラフ式)が、どのようにCleanで定義されるのかを説明する。関数の本体は、(ルート)式からなる(3.4参照)。パターン(3.2参照)とガード(3.3参照)の助けによって、複数の関数代替部定義を区別できる。関数と定数は、関数定義内に局所定義できる。プログラミングの便宜(評価の強制、一意オブジェクトの監視と順次演算のスレッド化)の為に、特別なlet構成を提供する(3.5.1参照)。関数の型付けは、セクション3.7で議論する。多重定義関数については、6章参照。一意データ型に作用する関数については、9章参照。


3.1 関数

 関数定義は、1つ以上の関数代替部(書換規則)定義からなる。そのような関数代替部の左手側では、(規則代替部(rule alternative)と呼ばれる)ガードされた関数本体(guarded function body)の並び全体を出力できるパターンを指定できる。特定の規則代替部のルート式(3.4参照)は、以下の場合に選択され評価される。
 代替部は、テキスト順に試行される。関数には、その型定義が先行できる(セクション3.7)。
 演算子とは、中置演算子(括弧を省略する)か通常の前置演算子(引数に先行する演算子名は、括弧に囲まれていなければならない)として使用できる、アリティ2を持つ関数である。演算子の型定義(3.7.1参照)で定義できる優先順位(0から9)と固定性infixleft、infixright、infix)は、式内の演算子適用の優先度を決める。より高い優先順位であれば、よりタイトに束縛される。演算子が同一優先順位を持つ場合、その固定性が優先度を決める。

3.2 パターン

 関数定義の左手側で指定されるパターンによって、関数の仮引数が指定される。関数代替部は、関数適用の実引数が仮引数に照合する場合にのみ選択される。仮引数は、定数(部分パターンから構成できるオプションの引数を持つデータ構成子)か変数のどちらかである。

 パターン変数は、(ノード)変数ワイルドカードであればよい。変数とは、関数の実引数のあらゆる具体的値に照合する、関数の仮引数であるので、この引数の評価を強制しないワイルドカードとは、対応する引数が関数の右手側では使用されないことを指示するのに使用できる無名変数("_")である。変数は、(記号'=:'を使用して)パターンに接着でき、そのことによって、その内容だけではなくパターン全体を識別(ラベル付け)できるようになる。定数(データ構成子)が仮引数として指定される場合には、実引数は、照合を成功させる為に、同一定数を保持していなければならない。

 関数の仮引数とその関数本体は、新しい局所通用範囲内に保持される。
 簡便性と効率性の為、定義済みの型とレコード型のデータ構造のパターン照合を表現する特別な構文を提供している。これらは、他の所で取り扱う(以下参照)。


3.3 ガード

 ガード(guard)とは、規則代替部に付けられた、パターン照合機構の汎化と見ることができる論理式である。つまり、左手側で定義されたパターンが照合し、且つ、その(オプションの)ガードがTrueに評価された場合にのみ、代替部が照合する(3.1参照)。そうでなければ、次の関数代替部が試行される。パターン照合は常に、ガードが評価される前に発生する。

 このガードは、テキスト順に試行される。Trueを出力する最初のガードに対応する代替部が評価される。ガードを持たない右手側は、常にTrueと評価されるガードを持つものと見ることができる('otherwise'又は'デフォルト'ケース)。キーワードでは、otherwiseは、デフォルトオプションを強調したい人々にとっては、Trueのシノニムである。
 ガードは、入れ子にできる。あるレベルのガードがTrueに評価される場合、その次のレベルのガードが試行される。

3.4 式

 関数の主たる本体は、ルート式と呼ばれる。ルート式は、グラフ式である。

 式は一般に、実引数に対する関数の適用か又は、単純にデータ構成子を引数に適用することによるデータ構造の(自動的)生成を表現する。各関数又はデータ構成子は、カリー化された方法で使用できるので、任意数(0以上)の引数に適用できる。0引数に適用される関数と構成子は、構文上の一単位を形成するだけである(この場合、非演算子には、括弧は不要である)。
 演算子とは、中置で適用できる、アリティ2を持って定義された特殊な関数又はデータ構成子である。演算子の型定義内で定義可能な優先順位(0から9)と固定性infixleft、infixright、infix)は、式内の演算子適用の優先度を決める。より高い優先順位であれば、よりタイトに束縛される。演算子が等しい優先順位を持つ場合、固定性が優先度を決める。式内では、通常の関数適用は、非常に高い優先度(10)を持つ。レコード要素(5.2.1参照)と配列要素(4.4.1参照)の選択だけが、よりタイトに(11)束縛される。その他に、その優先度によって、括弧を省略できる場合がある。つまり、演算子適用は、ちょうど他の適用のように動作するのである。
 グラフ式内に現れることのできる変数には2種ある。つまり、関数の仮引数として導入される変数(3.1と3.2参照)と、(グラフ式の部分を識別する選択子内に定義される)選択子変数(3.6参照)である。

 簡便と効率性の為に、特別な構文を提供して、定義済みの型と、特種な代数型とみなされるレコード型のデータ構造の式を生成する。これらは他の所で取り扱う。

3.4.1 ラムダ抽象

 "正に即席で"式内に小さな関数を定義することが便利な場合がある。この為に、ラムダ抽象(lambda abstraction)を定義できる。通常の関数定義では一般的なように、パターンであってもよい複数の仮引数を持つことのできる無名関数が定義される(3章参照)。しかし、この方法では、シンプルな関数しか定義できない。つまり、ガード、規則代替部と局所定義はない。

 CLEAN 1.3との互換性の為、関数本体から仮引数を分離するのに矢('->')を使用することも認められている。

 ラムダ式は、新規の通用範囲を導入する(2.1参照)。

3.4.2 Case式と条件式

 プログラミングの簡便の為に、case式条件式(conditional expression)を加えている。

 case式では、まず、弁別する式が評価され、その後、case代替部がテキスト順に試行される。case代替部は、関数代替部に似ている。これはそう奇妙なことではない。というのも、case式は内部的には、関数定義に翻訳されるからである(以下の例参照)。各代替部は、左手側パターン(3.2参照)を持ち、それには、let-before(3.5.4参照)とガード(3.3参照)がオプションとして後続する。パターンが照合し、オプションのガードがTrueに評価されると、それに対応する代替部が選択される。各case代替部には、新しいブロック構造(通用範囲)が生成される(2.1参照)。

 CLEAN 1.3.xとの互換性の為に、case代替部を分ける矢('->')を使用することも認められている。
 条件式では、まず、論理式が評価され、その後、then又はelse部が選択される。条件式は、単純な種類のcase式と見ることができる。

3.5 局所定義

 局限された通用範囲を持ち、モジュール全体に渡っては可視的でない定義を導入することが便利である場合がある。局所通用範囲、つまり、特定のプログラム領域でしか意味を持たない関数を定義できる。その通用範囲の外側では、その関数は見えない。この局所性は、より良いプログラム構造を得るのに使用できる。つまり、特定のプログラム領域でしか使用されない関数は、その領域の外側では隠されたままでいることができる。

 関数の他に、定数選択子を定義することも便利な場合がある。定数は、グラフ式と名付けられている(3.6参照)。

3.5.1 let式:式内の局所定義

 let式とは、局所関数と定数を定義可能な式内に新しい通用範囲(2.1参照)の導入を可能にする式である。このような局所定義は、以下の構文を持つlet式を使用すれば、式内のどこででも導入できる。


3.5.2 whereブロック:関数代替部内の局所定義

 各関数代替部の最後では、whereブロックの中に関数と定数グラフを局所的に定義できる。

 whereブロック内で定義された関数とグラフは、whereブロックの通用範囲を示す以下の図で示されるように、対応する関数代替部(つまり、パターンに従う全てのガードと規則代替部、3.1参照)内のどこででも使用できる。
 この通用範囲規則は、周囲の関数代替部の仮引数が、局所定義された関数とグラフには可視的であるように存在していることに注意。従って、引数は、局所定義内で直接使用できる。このような局所定義は、常に明示的に型付けしなければならない訳ではない(3.7参照)。

3.5.3 Withブロック:ガードされた代替部内の局所定義

 withブロックを使用すると、各ガード規則代替部の最後でも、関数とグラフを局所定義できる。

 withブロック内で定義された関数とグラフは、withブロックの通用範囲を示す以下の図で指示されるように、対応する規則代替部の中でのみ使用できる。
 この通用範囲規則は、周囲のガードされた規則代替部の引数が、局所定義された関数とグラフに可視的であるように存在することに注意して欲しい。従って、引数は、局所定義において直接に指示できる。このような局所定義は常に、明示的に型付けしなければならない訳ではない(3.7参照)。

3.5.4 Let-Before式:ガード間に定義された局所定数

 CLEAN I/Oライブラリの入出力関数の多くは、状態遷移関数である。このような状態はしばしば、ある関数からまたある別の関数に、シングルスレッドな方法で渡され(9章参照)、特定の評価順序を強制する。これは、この状態が一意型である場合には確実に当て嵌まる。このスレッド化した引数は、その様々なバージョンを識別する為に改名されなければならない。以下の例は、典型的な例を示している。

 スレッド化された引数のこの明示的な改名は、非常に醜く見えるだけではなく、この種の定義を読むのも困難である場合がある(どの順序で何が起こっているのか?どの状態がどんな状況で渡されているのか?)。我々は次のことを認めなければならない。即ち、命令型のプログラミングスタイルの方が、I/Oを行う場合のような、物事が特定の順序で発生しなければならない場合には、読むのが遥かに簡単である。そういう訳で、let-before式を導入した。

 let-before式は、ガード又は関数本体の前に定義できる特別なlet式である。この方法では、意図した発生順序で逐次的な動作を指定できる。let-before式は、以下の構文を持つ。

 びっくりマーク(#!)を持つ形は、定義の左手側に現れるノードidの評価を強制する。Let-before式内では定数選択子(グラフ定義)しか定義できないことに注意。関数は定義できない。

 let-before式は、命令型プログラミングの外見を得る為に、特別な通用範囲規則を持っている。これらの定義の左手側の変数は、その定義の右手側の通用範囲内には現れないが、後続の他の定義の通用範囲内には現れる(ルート式は含むが、whereブロックの局所定義は含まない)。
 let-before式内で定義された変数は、where式内では使用できないことに注意。その逆は真である。つまり、where式内の定義は、let before式内で使用できる。

 この記法は危険でもあり得る。つまり、様々な地点で同一名が使用される一方で、名前の意味は常に同じではない(定義から定義に渡って変化する通用範囲を考慮しなければならない)。しかし、この記法は、一意型の引数をスレッド化するのに使用する場合にはかなり安全である。型システムは、このような引数が、正しくシングルスレッドな方法で使用されない場合に、それを指摘するだろう。その他の場合に命令型のプログラミングスタイルを採用することについては、let before式の使用を勧めない。


3.6 定数の定義

 定数式(実際は、グラフ)を他の式で使用できる(そして、その他の式が共有できる)ように、その式に名前を与えることができる。選択子と呼ばれる射影関数を経由して、定数の特定の部分を識別することもできる(以下参照)。

 実際にグラフが定義されると、式(の一部)に名前が与えられる。グラフの定義は、定数(データ)又は定(射影)関数の定義と比較できる。しかし、複数の同一グラフへの参照はそのグラフの共有をもたらすという意味を持つCLEANの基礎の意味論(1章参照)に従って、グラフは構成されるということに注意して欲しい。再帰的な参照は、循環グラフ構造(cyclic graph structure)をもたらすだろう。グラフは、それらがただ一回だけしか計算されず、その値は、定義されている通用範囲内で記憶されているという特性を有している。

 グラフ定義は、定関数定義とは異なる。定関数定義は、アリティ0で定義された関数に過ぎない(3.1参照)。定関数定義は、通常のグラフ書換規則を定義する。つまり、ある関数への複数の参照は、(定)関数が、作られる関数記号の各出現毎に再計算されるように、それの同一定義が使用されるということを意味するに過ぎない。この違いは、関数定義の時間及び領域動作に影響を持つ場合がある(10.2参照)。

 構文の上では、グラフの定義は、右手側から左手側を分割する記号によって、関数の定義と区別される。つまり、"=:"はグラフに使用されるが、関数には"=>"が使用される。しかし、一般的には、より一般的な記号"="が両方の型定義に使用される。一般に、その文脈から、意味されるものは明確である(関数は引数を持つ、選択子も認識が容易である)。しかし、シンプルな定数が定義される場合、その構文は曖昧である(これは、定グラフ定義でもあり得るし、定関数定義でもあり得る)。

 可能な限りいつでも"="の使用を認める為に、以下の規則に従う。局所定数定義は、デフォルトではグラフ定義とみなされ、従って、共有されるが、大域的には、それはデフォルトでは関数定義とみなされ(3.1参照)、従って、再計算される。もし異なった動作を得たいならば、"=:"(大域レベルで、共有される定数グラフを意味する)又は"=>"(局所レベルで、定関数であり、再計算されなければならないことを意味する)を使用することによって、定数定義の性質(共有されなければならないか、再計算されなければならないか)を明示的に述べなければならない。

 ガーベッジコレクタは、プログラムグラフのルートに最早繋がっていない局所定義グラフを集める(1章参照)。

選択子

 グラフ定義の左手側は、シンプルな名前でもよいが、選択子と呼ばれるより複雑なパターンでもよい。選択子(selector)とは、定義されている定数グラフ(の一部)を識別する為に、射影関数(projection function)を黙示的に定義する、1つ以上の新しい選択子変数を導入するパターンである。部分グラフを全体として識別したり、その部品を識別したりできる。選択子は、定数(代数型定義によって導入されるユーザー定義定数でもある)、変数及びワイルドカードを保持できる。ワイルドカードによって、特定の部品には関心がないことを指示できる。

 選択子は大域定義できない。これは、let(3.5.1参照)、let-before(3.5.4参照)、whereブロック(3.5.2参照)とwithブロック(3.5.3参照)で定義できるのみである。更に、選択子は、リストの内包表記(4.2.1参照)と配列の内包表記(4.4.1参照)の生成子の左手側に現れることができる。


3.7 関数の型付け

 一般的には、関数の型を明示的に指定する義務はないが(CLEANコンパイラは、型を一般的に推論できる)、明示的な型仕様は、プログラムの可読性増大の為に高く勧められる

 関数がエクスポートされる場合や、プログラマが関数適用に追加的な制限を課したい場合には、明示的な仕様が必要である(例えば、より制限的な型を指定できる。10.1で説明されるように、正格性情報を追加できる。7章で説明されるように、多重定義の表現の為に、型変数にクラス文脈を定義できる。3.7.5(正格な引数を持つ関数)で説明されるように、一意性情報を付加できる。)。

 CLEAN型システムは、Milner/Mycroft型割当ての組合せを使用している。これは、結果として、ある稀な場合には、型システムは、与えられた型を承認できる(Mycroftシステムを使用している。PlasmeijerとVan Eekelen, 1993参照)が、関数の型を推論できない(Milner/Hindleyシステムを使用している)ということをもたらす。また、ランク2の全称量化型(3.7.4参照)が使用される場合、プログラマによる明示的な型付けを必要とする。

 関数の型仕様には、デカルト積(カルテシアン積、直積)が使用される。デカルト積は、[]で囲まれた引数の型の並記によって表される。単一引数の場合には、[]を省略できる。型の仕様内では、型構成子の適用の束縛優先度は、矢->の束縛よりも高い。演算子を定義することを指示する為に、関数名は、左手側で、()によって囲まれる。

3.7.1 カリー化関数の型付け

 CLEANでは、全ての記号(関数と構成子)は、固定したアリティで定義される。しかし、適用では、それらを任意数の引数に適用することが、勿論認められている。関数のカリー化適用は、そのアリティより少ない引数の数を持つ関数の適用である(CLEANでは、関数のアリティは、その型から導出できることに注意)。定義済み内部関数_APの助けによって、必要とされる数の引数に適用されるカリー化関数は、同等なカリー化関数適用に変換される。

 CLEAN型システムの型公理には、アリティnで定義された全てのsに対して、s :: (t1->(t2->(...(tn->tr)...))が、s :: t1 t2 ... tn -> trと同等であるということが含まれる。

3.7.2 演算子の型付け

 演算子(operator)とは、中置で使用できるアリティ2を持つ関数である。演算子は、関数定義の左手側の()の間の演算子名を囲むことによって、定義できる。演算子は、優先順位(precedence)(0から9、デフォルトは9)と固定性(fixity)infixlinfixr又は単にinfix、デフォルトはinfixl)を持つ。より高い優先順位であれば、よりタイトに束縛される。演算子が等しい優先順位を持つ場合には、固定性がその優先度を決める。式内では、通常の関数適用は、常に最高の優先度(10)を持っている。セクション3.1と3.4も参照。

3.7.3 部分関数の型付け

 パターンとガードは、書換規則を適用できる前に果たされなければならない条件を示している(3.2と3.3参照)。このことによって、部分関数(partial function)、つまり、指定した型の全てのあり得る値に対しては定義されていない関数を定義することが可能となる。
 abort式(StdMisc.dcl参照)によって、あらゆる部分関数を全体関数(total function)に変更できる(abort式はあらゆる型を持つことができる)。abort式は、ユーザー定義の実行時エラーメッセージを与える為に使用できる。

3.7.4 関数の型に全称量化子を明示的に使用

 多相関数の型をCLEANで指定する場合、一般的には、全称量化子を省略する。

 未実装:Clean 2.0では、全称量化子を明示的に書き下すことが認められている。関数の型定義の::の直後に、量化子A.(for all)を書き下すことができる。この方法では、関数の型定義に使用される型変数を明示的に導入できる。通常は、そのようにして導入された型変数は、通用範囲としては、関数型定義全体を持つ。

 実装済み:Clean 2.0では、ランク2多相性を提供する。つまり、通用範囲として、関数の引数の型又は関数の結果の型に、全称量化子を指定することもできる。これによって、引数として、多相関数を、そうでなければ単相的に扱われていた関数に渡すことができる。ランク2多相性を使用することの利点は、より多くのプログラムが型システムによって承認されるということだが、その場合には、関数の型に、(全称量化子を書き下すことによって)そのような多相関数が引数として取られ、結果として出力されることを明示的に指定しなければならない。

3.7.5 正格な引数を持つ関数

 関数の型定義では、引数が正格であるとして任意に注釈を付けることができる。関数について推論する際には、関数書換え発生前に、それに対応する引数が強ルート正規形の中にある(2.1参照)ということが常に真であるだろう。一般に、正格性情報は、実行効率性を増大する(10章参照)。

 CLEANコンパイラは、抽象簡約(abstract reduction)(Nocker, 1993)に基づく高速で優れた正格性アナライザを持つ。コンパイラは、上記例のような多くの場合に、関数引数の正格性を導出できる。従って、一般的に、自分の手で関数の型に正格性注釈を加える必要は無い。関数がモジュールからエクスポートされる場合(2章参照)、定義モジュール内でその型を指定しなければならない。最適な効率性を得る為には、プログラマは、定義モジュールの型定義にも正格性情報を付けなければならない。ただ、コンパイラに、導出された正格性情報を持つ型を印字し、これを定義モジュールに貼り付けるよう指示することはできる。

First Uploaded : December 26, 2002
Last Modified : January 19, 2003

Back