目次へ 最終更新 : 2004/4/30
ダイナミック(Dynamic)は、CLEANの新しい実験的な特徴である。この概念を理解するのは容易だが、その実装はそう簡単ではない(VervoortとPlasmeijer,2002参照)。従って、全部を実装し、システムが完全に動作するには幾分時間がかかるだろう。待っていて欲しい。
"ダイナミック"で何ができるのだろうか?容易で型安全な方法で、(異なった)CLEANアプリケーション間の式を保存し、交換することができる。式は、(未評価!)データと(未評価!)関数適用を持つことができる。ここのその使用例をいくつか示す。
- ほとんど全てのアプリケーションは、(環境等のような)ディスクへ情報を保存し、ディスクから情報を取得する。伝統的に、ファイルに書かれた情報はまず、プログラマによって、ある(String)形式に変換されなければならない。ファイルが再度読込まれると、入力を解析し、Stringを適切なデータ構造に変換する為に、パーサを構築しなければならない。Dynamicによって、(ほとんど)あらゆるCLEANデータ構造を、型安全な方法で、たった一度(!)の関数呼出で、保存・取得できる。データだけでなく、保存されるデータ構造の一部であるコード(未評価関数、高階関数)も保存できる。Dynamicによって、永続的なアプリケーション(persistent application)を書くことが更に容易になる。アプリケーションは、そのような方法で環境を保存し、次の時には、ユーザーは、アプリケーションが使用された最後の時と同じ方法で全てを見るのである。
- 様々に独立して打ち込まれたCLEANアプリケーション、ネットワークを通じて配布されているアプリケーションさえ、型安全な方法で、容易に、データだけでなくコード(未評価関数)を保持できる任意の式を通信できる。Dynamicは、ファイルやCLEANライブラリが提供するメッセージ渡しプリミティブを経由して、通信できる。CLEANアプリケーションがコードを通信できるという事実は、実行中のCLEANアプリケーションを追加する機能を拡張できるということを意味している。従って、プラグイン(plug-in)とモバイル(mobile)コードを非常に簡単に実現でき、全ては型安全である。
これら全てを可能にする為には、CLEAN言語にいくつかの特別な機能が必要である。しかし、CLEANの実行時システムの特別なサポートも必要である。この中には、動的型検査(dynamic type checking)、動的型単一化(dynamic type unification)、任意のCLEAN式及び型の動的エンコード・デコード(dynamic encoding and decoding)、動的連係(dynamic linking)、ディスク上の動的オブジェクトのガーベッジコレクション(garbage collection)とジァストインタイムなコード生成(just-in-time code generation)が含まれる。
CLEAN 2.0では、動的型システム(dynamic type system)を加えたので、CLEANは現在では、静的型付き(static typing)と動的型付き(dynamic typing)の両方を有する混合型システム(hybrid type system)を提供している。静的な型のオブジェクト(式)を包んで、動的な型のオブジェクト("Dynamic")にすることができるし、その逆も同様である。Dynamicの型は、実行時にのみ検査できる。アプリケーションは、複数のDynamicの型が、どの型がDynamicに正確に保存されるかを知る必要なく、互いに単一化できるかを検査することもできる。この方法では、CLEANアプリケーションは、プロセスとプラグインを管理する制御言語として使用できる(Van WeeldenとPlasmeijer, 2002参照)。
本章では、まず、Dynamicがどのように構築できるのかを説明する(8.1参照)。セクション8.2では、Dynamicの型が、パターン照合を経由してどのように検査できるのか、そして、実行時型単一化を使用して、Dynamicが適合するのをどのように保証できるのかを説明する。
セクション8.3では、独立に実行しているアプリケーション間の式を型安全に通信するのに、どのようにダイナミックを使用できるのかを説明する。セクション8.4では、これらを全て可能にするCLEAN実行時システムの基本的なアーキテクチャを説明する。意味的な制限と現在の実装に関する制限は、セクション8.5にまとめられている。
CLEANは強く型付けされた言語なので(5章参照)、CLEANの式は全て、コンパイル時に決定される静的な型を有する。CLEANコンパイラは一般に、あらゆる式又はあらゆる関数の静的な型を推論できる。
CLEANの式とその静的な型の例:
3::Int
map::(a -> b) [a] -> [b]
map ((+) 1)::[Int] ->[Int]
MoveCollorPoint Green::(Real,Real) -> ColorPoint
キーワードdynamicを使用することによって、(原則として)静的な型::τのあらゆる式を、静的な型::Dynamicである動的に型付けされたオブジェクトに変更できる。この"dynamic"は、元の式だけでなく、式の元の静的な型のエンコーディングも含むオブジェクト(正確にはレコード)である。式とその静的な型のエンコーディングの両方がダイナミックに包まれる。実行時には、ダイナミックの内容(式の値とそのエンコードされた型)を、動的パターン照合を経由して検査できる(8.2参照)。
動的式 = dynamic グラフ式 [:: 型]
式をDynamicに包むことができる方法を示す例。
オプションとして、ダイナミックに包みたい式の静的な型を指定できる。
dynamic 3
dynamic 3::Int
dynamic map::A.a b:(a->b) [a] -> [b]
dynamic map::(Int -> Real) [Int] -> [Real]
dynamic map ((+) 1)
dynamic MoveCollorPoint Green
型Tree Intの式を含むダイナミックを生成する(定)関数の例
:: Tree a = Node a (Tree a) (Tree a) | Leaf
MyTree::Dynamic
MyTree = dynamic (DoubleTree 1 mytree)
where
Doubletree rootvalue tree = Node rootvalue tree tree
mytree = (Node 2 (Node 3 Leaf Leaf) Leaf)
コンパイラは、式をその型と組合せてダイナミックにすることができるだけなので、エンコードされた型は実際にはそれに対応する包まれた式の型であることが保証される。しかし、通常のように、コンパイラが推論する型より特定された型を指定することが認められている。コンパイラは、そのような(制限された)型仕様の正当性を検査するだろう。多相型も保存できる。
- 多相型の式をダイナミックに包む場合、全称量化子も明示的に指定する必要がある(上記例参照)。
原則として(2、3の例外はある)、基本型、関数の型、ユーザー定義型、多相型、レコード型、配列の全ての型、リストの全ての型と存在量化型を含めて、あらゆる代数データ型をダイナミックに包むことができる。システムは、シノニム型も処理できる。制限は、抽象データ型、一意性型と多重定義関数を包む際に妥当する。以下のサブセクションを参照。
キーワード"dynamic"で生成されたオブジェクトの静的な型は、定義済みの型Dynamicである。この方法で生成された全てのオブジェクトは、型Dynamicであり、コンパイラは一般に、Dynamicに隠された静的な型をこれ以上決定できないし、その型一貫性をこれ以上検査できない。Dynamicに保存される型は、実行時に検査されなければならない(8.2と8.3参照)。
特定の型は単純に、根本的な理由から、Dynamicに包むことはできない。型Worldと型Fileのオブジェクトのような、現実世界に特別な意味のある定義済みの抽象型のオブジェクトがその例である。これらの型のオブジェクトをDynamicに包むのは健全ではない。というのも、これらの現実世界の対応物は、包んだり保存したりできないからである。
- (World、Fileのような)現実世界に意味のある抽象データ型は、Dynamicに包むことはできない。
これらの場合には、コンパイラ時エラーメッセージが与えられ、コンパイラは、式をDynamicに包むことを拒絶する。
現在の実装では、Dynamicに包むことのできる型の種に関して、追加的な制限が存在する。現在のところ、抽象データ型をダイナミックに包むことは全くできない。その理由は、抽象型の等値性テストが簡単ではないということである。つまり、関連する型の定義の等値性をテストするだけでは十分でない。更に、これらの抽象データ型に関して定義されている演算が全く同じかどうかを検査しなければならない。これを行う最も直截な方法は、抽象データ型が同一モジュール(レポジトリ)から来ていることを必要とすることであろう。
- 抽象データ型のオブジェクトを持つ式は、ダイナミックに包むことはできない。これに関しては目下作業中である。Cleanシステムの最新版をチェックして下さい。
未実装:多重定義関数も、Dynamicに包むことができる。この場合、関数の追加的な辞書引数として、それに対応する型クラス(6.1参照)をDynamicに包む。Dynamicは、パターン照合で開包され、同一の型クラスが受信関数で定義されていなければならないが、そうでなければ、パターン照合は失敗する(8.2.2参照)。
例:多重定義関数をDynamicに保存する。
OverloadedDynamic:: Dynamic
OverloadedDynamic = dynamic plus :: A.a:a a -> a | + a
where
plus:: a a -> a | + a
plus x y = x + y
- 現在のところ、多重定義関数がダイナミックに包まれる場合、全称量化子とクラス文脈を含めて、その型を明示的に指定しなければならない。
未実装:一意型の式(9章)も、ダイナミックに包むことができる。しかし、実行時システムは、一意性型変数や型変換ステートメント(属性変数の不等式)を処理できない。型属性"*"のみを使用できる。更に、Dynamicに包まれる式の型に関する求めた一意性を明示的に定義しなければならない。そうでなければ、包まれた式の一意性は、保存されない。いつものように、コンパイラは、包まれた式の指定した型(一意性型属性を含む)が正当であるかを検査する。
例:文字を一意ファイルに書き込むことのできる関数をDynamicに包む。
MyDynamic:: Dynamic
MyDynamic = dynamic fwritec :: Char *File -> *File
- 一意性型変数と型変換ステートメントは、Dynamicに包まれる型の部分にはなり得ない。
- 一意性型属性は、包まれるダイナミックの型の中で明示的に指定される場合にのみ考慮される。
反例:Dynamicは、一意性型変数又は属性変数不等式を処理できない。
MyDynamic :: Dynamic
MyDynamic = dynamic append :: [.a] u:[.a] -> v:[.a] , [u<=v]
コンパイラは、Dynamicに割当てられるべき具体型を、全ての場合において推論できるわけではない。例えば、多相関数が定義されている場合、実引数の型が何になるかは一般的には分からない。それが多相的であれば、それはどの型でもよい。
- 多相型の引数は、ダイナミックに包むことができない。
Dynamic生成関数の反例。多相型の引数は、Dynamicに包むことはできない。
WrongCreateDynamic :: t -> Dynamic
WrongCreateDynamic any = dynamic any
任意の引数をDynamicに包むことのできる関数を定義したい場合、その引数の値だけでなく、具体的な静的型もその関数に渡されなければならない。効率性の理由から、全ての引数の型を全ての関数に渡すことは勿論したくない。従って、引数のどれがDynamicに包まれ得るのかを知らなければならない。特別なクラス文脈制限がこれを表現するのに使用される。従って、多相関数の代わりに、多重定義(6章参照)関数を定義しなければならない。クラスTC(Type Code)は、定義済みであり、未知の型の引数がダイナミックに包まれる場合にはいつでも、このクラスのインスタンスが必要とされる。コンパイラは、必要な場合には、TCの適切なインスタンスを自動的に生成するだろう。
任意の型の引数をDynamicに包むことのできる多重定義関数の例
CreateDynamic :: t -> Dynamic | TC t
CreateDynamic any = dynamic any
MyTree :: Dynamic
MyTree = CreateDynamic (Node 2 (Node 3 Leaf Leaf) Leaf)
動的型付けはまた、静的型システムが禁止していることを行うのに使用できる。例えば、リストは、リストの全要素が同型であることを必要とする。全ての動的式は型Dynamicなので、静的な様々な型のオブジェクトをDynamicに変えることによって、これらを一つのリストに組み合わせることができる。
例:様々な型のオブジェクトを1つのリストに包む3つの方法。
第1の方法は、1つのリストに包みたい全ての型が、
それらを区別する適切な構成子によってまとめられているような新規の型を定義することである。
開包するには、パターン照合の中に、様々な構成子に関するケースの区別を作ることができる。
全てが良くて、静的に型付けされているが、ラッパー型内で言及される型を開閉することしかできない。
:: WrapperType = I Int | R Real | C Char
MyWrapperList = [I l, R 3.14, C 'a']
様々な型のオブジェクトを包む次の方法は、存在型(5.1.3参照)を使用して、リスト構造を定義することである。
ここでは、どんな型も包むことができるが、その欠点は、一度包まれると、要素を区別し、
それらを開包する簡単な方法が存在しないということである。
:: ExstList = E.a: Cons a ExstList | Nil
MyExstList = Cons l (Cons 3.14 (Cons 'a' Nil)
第3の方法は、その値をダイナミックに包むことである。どんな型でも包むことができるし、
パターン照合を経由して、それらを開包することもできる。しかし、これは非常に非効率なので、
パターン照合でその型を明示的に名付けることによってのみ、値だけを開包できる(8.2参照)。
MyDynamicList = [dynamic l, dynamic 3.14, dynamic 'a']
全ての引数が動的に型付けされているCLEANプログラムを書くことができる。することはできるが、するのは良いことではない。つまり、ダイナミックを持つプログラムは、信頼性に劣るし(実行時型エラーが発生するかもしれない)、遥かに非効率である。
Dynamicは、意図した目的、つまり、型安全な格納、取得及び、(独立した)アプリケーション間の任意式の通信に使用すべきである。
Dynamicが生成される場合(上記参照)、その静的な型は、型Dynamicである。コンパイラは一般に、Dynamicに包まれている式の元の型が何であるかを決めることはできない。それを理解する唯一の方法は、実行時に(この故にDynamicと呼ばれている)、パターン照合(3.2参照)又はcase構成物(3.4.2参照)を経由してダイナミックを調査することである。Dynamicに関するパターン照合によって、Dynamicに包まれた式の値だけでなく、その元の型も調査できる。従って、Dynamicによって、CLEANでは、実行時型検査と動的型単一化が可能である。そして、動的型システムの長所(より多くの式を型付けできる)と短所(型検査が実行時に失敗する恐れがある)を全て持つことになる。プログラマは、非照合の動的型によって、パターン照合が失敗する場合の処理に注意しなければならない。
動的パターン = (グラフパターン :: 動的型)
動的型 | { 動的パターン型}+
動的パターン型 = 型
| 型パターン変数
| 多重定義型パターン変数
型パターン変数 = 変数
多重定義型変数 = 変数^
ダイナミックに包むことのできる式は、動的パターン照合を使用して開包することもできる。Dynamicに関するパターン照合によって、Dynamicの内容に関してケースの区別を作ることができる。実際のDynamicが型とパターンで指定された値に照合すれば、それに対応した関数代替部が選択される。Dynamicが指定した型に照合するケースは分かっているので、静的型システムは、その知識を使用する。つまり、既知の型のダイナミックは、関数本体の通常の式として処理できる。この方法では、ダイナミックを通常の静的な型付けされた式に再び変換し戻すことができる。静的型チェッカーは、その知識を使用して、対応する関数代替部本体の型一貫性を検査する。
例:動的パターン照合を使用して、Dynamicが特定の定義済みの型であるかを検査する。
関数transformの第1代替部は、Dynamicが値0の整数を持っているかを照合する。
第2代替部は、Dynamicが(0以外の)整数を持っていれば選択される。
第3代替部は、[Int]から[Int]への関数を要求している。
その次の代替部は、Dynamicが2つの[Int]のペアである場合に選択される。
代替部のどれにも照合しない場合、最後の代替部が選択される。その場合、プログラムは空リストを出力する。
transform :: Dynamic -> [Int]
transform (0 :: Int) = []
transform (n :: Int) = [n]
transform (f :: [Int]->[Int]) = f [1..100]
transform ((x,y) :: ([Int],[Int])) = x ++ y
transform other = []
警告:ダイナミックに関するパターン照合を定義する場合、パターン照合が失敗するかもしれないということを常に認識していなければならない。従って、非照合のダイナミックを処理できる代替部を常に含めることを勧める。そうでなければ、アプリケーションは、関数代替部のどれも照合しなかったというエラーメッセージを発して終了するだろう。
例:動的パターン照合を使用して、Dynamicが特定のユーザー定義代数データ型であるかを検査する。
DynamicがIntのTreeを持つ場合、関数CountDynamicLeafsは、この木の葉の数を数える。
そうでなければ、CountDynamicLeafsは0を返す。
:: Tree a = Node a (Tree a) (Tree a) | Leaf
CountDynamicLeafs :: Dynamic -> Int
CountDynamicLeafs (tree :: Tree Int) = countleafs tree
CountDynamicLeafs other = 0
where
countleafs :: (Tree Int) -> Int
countleafs tree = count tree 0
where
count:: (Tree a) Int -> Int
count Leaf nleafs = nleafs + 1
count (Node left right) nleafs = count left (count right nleafs)
MyTree :: Dynamic
MyTree = dynamic (Node 1 (Node 2 (Node 3 Leaf Leaf) Leaf) (Node 4 Leaf Leaf))
Start :: Int
Start = CountDynamicLeafs MyTree
例:動的パターン照合を使用して、Dynamicが多相関数(この場合、恒等関数)であるかを検査する。
TestId :: Dynamic a -> a
TestId (id :: A.b: b -> b) x = id x
TestId else x = x
- 型パターン変数(8.2.4と8.2.5参照)との混乱を避けるには、多相型変数に、全称量化子(A.)を明示的に導入しなければならない。
- 量化子は、最外レベル(ランク1)でのみ認められている。
Dynamicは、他のモジュールの関数が生成することができるし、他の(全く異なった)Cleanのアプリケーションから発生することさえ可能である(8.3参照)。従って、Dynamicでは、特定の名前を持つ型を保存することが可能だが、この型は、照合関数において分かっている型とは(若干又は全く)異なっている型定義を持っているかもしれない。従って、ダイナミックが包まれる文脈は、パターン照合を経由してダイナミックが開包される文脈とは全く異なっているかもしれない。従って、型構成子を照合して名前が一致するというだけではな十分ではない。つまり、それらは型定義も全く同一でなければならない。そうでなければ、照合は失敗するだろう。
型定義(型構成子、データ構成子、クラス定義)の全てが、型変数の名前を除いて(α変換は認められる)構文的に同一である場合にのみ、2つの型は等しいとみなされる。型構成子の型の同等性は、これらが全く異なったCleanアプリケーションで定義されているとしても、Clean実行時システムが自動的に検査する。これを可能にする為には、Clean実行時システムのアーキテクチャ(8.4参照)を変更しなければならなかった。
従って、ダイナミックに関するパターン照合が発生する場合、指示した順番に以下のことが検査される(case構成物は類似の方法で処理される)。
- 動的パターンで指定される(基本型、定義済み、ユーザー定義のどれかの)型構成子全ては、ダイナミックに保存されているそれに対応する実際の構成子の名前と比較される。対応型構成子が異なった名前ならば、パターン照合は失敗し、その次の代替部が試行される。
- パターン照合では、対応型構成子が同一名を持つ場合、実行時システムは、その型定義(その型は異なったCleanアプリケーション又はCleanモジュールで定義されていたかもしれない)も同一であるかを検査するだろう。システムは、これらの型定義を発見する方法を知っている(8.3と8.4参照)。定義が同一でない場合、その型は、異なっているとみなされる。パターン照合は失敗し、その次の代替部が試行される。
- 型が同一である場合、ダイナミックの無い標準的なパターン照合(3.2参照)で通常のように、その実際のデータ構成子(定数値)が、パターンで指定されたデータ構成子と比較される。指定した定数の全てが実際の値と照合すると、その照合は成功し、それに対応する関数代替部が選択される。そうでなければ、パターン照合は失敗し、その次の代替部が試行される。
現在の実装では、Dynamicに包むことができ、従って、Dynamicから開包もできる型の種に関して、追加的な制限が存在する。
動的パターン照合では、特定の型構成子(例えば、Tree Int)に関して照合するよう明示的に指定できる。型パターン変数(8.2.4参照)を使用して、実際の型が単一化されなければならない型スキームを指定することもできる。関数の型で定義されている多重定義変数(8.2.5参照)を使用することによって、関数が使用される静的な文脈は、受理されるダイナミックの種に影響を与えることができる。従って、型パターンで発生し得る変数には2つの特別な型がある。つまり、型パターン変数と多重定義型パターン変数である。
- 抽象データ型を開閉包することはまだできない。目下作業中である。Cleanシステムの最新版をチェックしておいて下さい。8.1.1も参照。
未実装:動的パターン照合においては、型のクラス制限を指定できる。システムは、実際のダイナミックが、パターンで指定されるのと全く同じクラス文脈で実際に多重定義されている関数を持つかを検査する。型変数のα変換を除いて、関連する全てのクラス定義が構文上等しい場合、2つのクラス定義は等しいとみなされる。
- 多重定義型変数の為に、パターンの全称量化子を経由してそれらを導入し、型パターン変数との混乱を回避しなければならない(8.2.4と8.2.5参照)。
- 量化子は、最上レベルでのみ認められている。
例:多重定義関数の開包。ダイナミックが+において多重定義されている関数を持つ場合にのみ、パターン照合は成功する。
それに対応するクラス定義が検査されるだろう。
つまり、クラス+の定義は、ダイナミックが生成された文脈で知られているクラス+と同じでなければならない。
plus 2 3の適用によって、型チェッカは、+の整数値に関するインスタンスを必要とするだろう。
CheckDynamic:: Dynamic -> Int
CheckDynamic (plus :: A.a : a a -> a | + a) = plus 2 3
CheckDynamic else = 0
未実装:一意型(9章参照)の式は、動的パターン照合を経由して開包もできる。しかし、実行時システムは、一意性型変数又は型変換ステートメント(属性変数不等式)を処理できない。型属性"*"を使用できるだけである。その照合は、指定した型が照合し、全ての型属性も照合する場合にのみ成功する。一意から非一意への変換又はその逆は発生しない。
例:文字を一意ファイルに書き込むことのできる関数を開包する。
WriteCharDynamic:: Dynamic Char *File -> *File
WriteCharDynamic (fwc :: Char *File -> *File) char myfile = fwc char myfile
WriteCharDynamic else char myfile = myfile
- 一意性型変数と型変換ステートメントは、動的パターン照合では使用できない。
- 仮引数と実引数の型属性は、全く同一でなければならない。一意から非一意への型変換やその逆は起こらないだろう。
通常のパターン照合では、データ構成子(引数が特定の値かをテストする)と(定義域のどの具体値にも照合する)変数を使用できる。同様に、動的型のパターン照合では、型構成子(ダイナミックが特定の型の式を持つかをテストする)と(どの型にも照合する)型パターン変数を使用できる。しかし、通常の変数と型パターン変数との間には違いもある。関数定義の左手側で導入される通常の変数記号は全て、異なった名前を持たなければならない(3.2参照)。しかし、同一の型変数記号は、関数定義の左手側(そして勿論、右手側)で複数回使用できる。型パターン変数は、通用範囲が関数の代替部であり、動的型の文脈において、関数の右手側だけでなくパターンにおいても現れることができる。
型パターン変数 = 変数
同一の型変数が左手側で使用される度に、パターン照合機構は、型変数とダイナミックに格納されている具体型を単一化しようと試みる。同一の型変数が左手側で複数回使用される場合、ダイナミックで全ての対応型に照合する最も汎用的な単一化子が決められる。汎用的な単一化子が見つからない場合、照合は失敗する。いつものように、全ての対応型構成子の中で、それらが実際に同じであるかが検査される。つまり、それに対応する型定義は、同等でなければならない(8.2参照)。照合している型構成子の型同等性は、その型が異なったCleanアプリケーションで定義されたものであるとしても、Clean実行時システムによって自動的に検査される。
型パターン変数は非常に便利である。これは、特定の型スキームの検査又は異なったダイナミック間の内部型一貫性の検査に使用できる。しかし、検査する関数は、どの具体型が実際にダイナミックに格納されているかを正確には知らなくともよい。型パターン変数を使用して、柔軟な方法で、プラグインを管理・制御できる(8.3参照)。
関数dynApplyは、型Dynamicの2つの引数を持ち、型Dynamicの値を出力する。
第1のDynamicは、型(a->b)に単一化できる関数を持たなければならず、
第2引数は、関数が期待する引数型aに単一化できなければならない。
この方法で、実際の型が何であるかを正確に知ることなく、
関数を引数に適用することが技術的に型安全であることを保証できる。
その結果は、静的な未知の型bであるだろうが、この結果を再びダイナミックに包むことによって、
静的型システムは幸せになる。
つまり、それはDynamicである。
動的な型が単一化できない場合、関数を引数に適用することは型安全ではないし、
dynApplyの第2代替部が選択される。
これは、ダイナミックに格納されているエラーメッセージを出力する。
dynApply :: Dynamic Dynamic -> Dynamic
dynApply (f :: a -> b) (x :: a) = dynamic (f x :: b)
dynApply df dx = dynamic ("cannot apply ",df," to ",dx)
Start = dynApply (dynamic (map ((+) 1)) (dynamic [1..10])
型パターン変数は、存在量化型変数(5.1.3参照)と似たように動作する。型パターン変数の実際の型が何であるかを静的に決定することはできない。従って、静的型システムでは、型が型パターン変数の値に依存している式を定義できない。静的型システムは、それを処理できない。というのも、その値を知らないからである。しかし、型が型パターン変数に割り当てられた型に依存している式ならば、再びダイナミックに包むことができる。というのも、これは実行時になされるからである。上のdynApplyの例を参照。
- 静的な型が型パターン変数の値に依存している式の生成は認められていない。
反例:静的な型を型パターン変数の値に依存させることはできない。
WrongDynApplyの型bの実際の値は、実行時には分からない。
この例は、型エラーをもたらすだろう。この関数の合法版については、8.2.5参照。
WrongDynApply :: Dynamic Dynamic -> ???
WrongDynApply (f :: a -> b) (x :: a) = f x
WrongDynApply df dx = abort "cannot perform the dyanmic application"
Start = WrongDynApply (dynamic (map ((+) 1)) (dynamic [1..10]) ++ [11..99]
注意:多相又は多重定義関数又は構成子を指示する、動的型に現れることのできる他の変数と、型パターン変数を混同しないように。前者は、型の全称量化子を経由して導入される。8.2.5も参照。
動的パターン照合では、Dynamicのどの型が要求されているのかを明示的に述べることができる。しかし、関数が使用されている静的文脈に、アクセスされるべきダイナミックへの制限を課させることも可能である。このことは、動的パターンの多重定義型変数(overloaded type variable)を使用することによって実現できる。この多重定義型変数は、関数それ自体の型の中に導入されなければならず、その変数は、文脈制限として、定義済み型クラスTC(8.1.1参照)を持たなければならない。このことによって、多重定義機構は、ダイナミックの中の要求された型が何でなければならないかを決めることができる(Marco Pil,1999で導入された"型依存関数(type dependent function)")。動的パターン内のこの多重定義型変数を使用することによって、静的多重定義機構によってこの変数に割り当てられる型が、動的パターン照合の必要な型の仕様として使用される。キャロット記号(^)は、型パターン変数ではなく多重定義型変数が使用されることを指示する為に、動的パターン照合の型パターン変数の接尾語として使用される。多重定義型変数は、通用範囲として、関数それ自体の型を含めた関数定義全体に及ぶ。
多重定義型変数 = 変数^
- 多重定義型パターン変数は、関数の型定義内に導入されなければならない。
- 定義済み型クラスTC(8.1.1参照)は、大域型パターン変数に関する文脈制限として指定されなければならない。
- 多重定義(6.6参照)で通常であるように、コンパイラが、例えば内部的に曖昧な多重定義である為にそれを解決できない場合がある。
例:関数Startは、FlexDynApplyの結果に[11..99]を加える。
従って、FlexDynApplyが関数Startに[Int]を渡さなければならないことを明らかである。
追加的な文脈制限TC bは、FlexDynApplyをbでの多重定義関数に変える。
関数FlexDynApplyは、動的型bではなく、FlexDynApplyを適用する文脈によって要求される静的型bを渡すだろう。
多重定義機構は、追加的な引数として、文脈に必要とされる静的型bに関する情報を自動的に渡す。
そして、この型は、動的パターン照合においてダイナミックの実際の型を検査するのに使用される。
FlexDynApply :: Dynamic Dynamic -> b | TC b
FlexDynApply (f :: a -> b^) (x :: a) = f x
FlexDynApply df dx = abort "cannot perform the dyanmic application"
Start = FlexDynApply (dynamic (map ((+) 1)) (dynamic [1..10]) ++ [11..99]
例:関数lookupは、Dynamicのリストの中から、特定の型aの値を検索する。
この関数が探す型は、関数lookupが使用されている文脈に依存する。
Startでは、lookup関数が二度使用される。
最初のケースでは、(+ 5である為)整数値が要求され、
第2のケースでは、(+ 2.5である為)実数値が必要とされる。
このプログラムは、必要な型の値がリストで見つけられない場合、異常終了する。
lookup :: [Dynamic] -> a | TC a
lookup [(x :: a^):xs] = x
lookup [x:xs] = lookup xs
lookup [] = abort "dynamic type error"
Start = (lookup DynamicList + 5, lookup DynamicList + 2.5) // 結果は(6,5.64)であろう。
DynamicList = [dynamic 1, dynamic 3.14, dynamic 'a']
注意:多重定義型変数を、型パターン変数又は、多相又は多重定義関数又は構成子を指示するのに動的型内で現れることのできるその他の変数と混同しないで欲しい。後者は、型内で、量化子を経由して導入される。
例:以下の人工的な例では、動的パターン内で使用できる型変数の種類を示している。
第1代替部では、型変数aが使用される(全称量化子によって導入される)。
この代替部は、多相関数に照合するだけである。
第2代替部では、多重定義型変数が使用され(a^で指示される)、
これは、関数変体に導入される多重定義型変数a | TC aを参照する。
これは、AllSortsOfVariablesの第2引数の実際の型と同じ型である関数に照合する。
最後の代替部は、型パターン変数aとbを使用している。この関数は使用されないが、
これはどの関数の型にも照合する。
AllSortsOfVariables :: Dynamic a -> a | TC a
AllSortsOfVariables (id::A.a : (a -> a)) x = id x
AllSortsOfVariables (f::a^ -> a^) x = f x
AllSortsOfVariables (f::a -> b) x = x
本章の導入で説明したように、Dynamicの最重要な実用例は、異なった(配布)CLEANアプリケーション間のデータとコードを型安全に通信できるということである。Dynamicが保存されるか通信される場合、それは文字列にエンコードされる(直列化される)。従って、原則として、ほとんどあらゆる通信媒体をDynamicの通信に使用できる。本セクションでは、Dynamicの保存・FileからのDynamicの取得方法のみを説明する。チャンネル又は送受信プリミティブを経由して直接にDynamicを通信することもできる。実際の機能は、CLEANライブラリが提供する能力に依存している。これは、本CLEAN言語報告の範囲外である。
CLEANアプリケーションは、DynamicをFileに保存する場合、他の全ての(全く違う)CLEANアプリケーションは、そこからダイナミックを読込むことができる。Dynamicがデータだけでなくコード(未評価関数適用)も持つことができるので、このことは、あるCLEANプログラムのどの部分もまたある別のプログラムに嵌め込むことができるということを意味している。CLEANはコンパイルされたコードを使用しているので、このことは、実行時システムに高い影響を与える(8.4参照)。
たった一回の関数呼出で、Dynamicを読み書きできる。CLEANライブラリStdDynamicでは、関数readDynamicとwriteDynamicが定義済みである。CLEANで通常のように、I/Oの実行には一意性型付けが使用される(9.1参照)。Dynamicが書込まれると、式全体(グラフ式とその静的な型)は、記号的にStringにエンコードされ、ディスクに保存される。Dynamicが読込まれると、式全体は遅延的に読込まれる。Dynamicの評価(パターン照合成功後にのみ発生し得る)が要求される場合にのみ、そのStringはDynamicにデコードされる。新規関数定義が嵌め込まれなければならない場合、自動的に行われる(8.4参照)。この遅延読込みは、Dynamicに保存されるダイナミックの為にも行われる。従って、Dynamicは、その型が動的パターン照合で承認される場合にのみ嵌め込むことができる。
Dynamicを読書きする標準関数
definition module StdDynamic
...
writeDynamic :: Dynamic String *World -> *(Bool,*World)
readDynamic :: String *World -> *(Bool, Dynamic, *World)
Dynamicの使用は、以下の例で示される。各例は、完全なCLEANアプリケーションである。
型Tree Intの値を持つDynamicを、DynTreeValueという名前のFileに書込むCleanアプリケーションの例。
この例は、CLEAN関数writeDynamicを使用して、データをディスクに保存できることを示している。
module TreeValue
import StdDynamic, StdEnv
:: Tree a = Node a (Tree a) (Tree a) | Leaf
Start world
# (ok,world) = writeDynamic "DynTreeValue" MyTree world
| not ok = abort "could not write MyTree to file named DynTreeValue"
| otherwise = world
where
MyTree::Dynamic
MyTree = dynamic (Node 1 mytree mytree)
where
mytree = (Node 2 (Node 3 Leaf Leaf) Leaf)
関数countleafsを持つDynamicを、CountsLeafsinTreesという名前のFileに書込む別のCLEANアプリケーション例。
この関数は、Treeの葉の数を数えることができ、型は(Tree Int) -> Intである。
この例は、WriteDynamicを使用するだけで、
コード(この場合は関数CountLeafs)もディスクに保存できるということを示している。
module CountLeafs
import StdDynamic, StdEnv
:: Tree a = Node a (Tree a) (Tree a) | Leaf
Start world
# (ok,world) = writeDynamic "CountsLeafsinTrees" CountLeafs world
| not ok = abort "could not write dynamic"
| otherwise = world
where
CountLeafs = dynamic countleafs
countleafs:: (Tree Int) -> Int
countleafs tree = count tree 0
where
count:: (Tree a) Int -> Int
count Leaf nleafs = nleafs + 1
count (Node left right) nleafs = count left (count right nleafs)
count else = abort "count does not match"
3つ目のCLEANアプリケーションは、ファイルから、Tree IntをもつTreeValueと、
Treeの葉の数を数えることのできる関数countleafs(プラグイン)を読込む。
従って、新しい機能が実行中のアプリケーションApplyに加えられる。
関数dynapplyを使用することによって、新しくプラグインされた関数countleafsが、読込まれた木にも適用される。
アプリケーションApplyそれ自体は、ノードの数を数える関数を持ち、この関数を読込まれる木に適用する。
このアプリケーションは、異なったアプリケーションで定義された全ての型Treeが(使用される型変数の名前を除いて)
全く同じである場合にのみ動作することに注意して欲しい。
module Apply
import StdDynamic, StdEnv
:: Tree a = Node a (Tree a) (Tree a) | Leaf
Start world
# (ok,countleafs,world) = read "CountsLeafsinTrees" world
| not ok = abort ("could not read CountsLeafsinTrees")
# (ok,treevalue,world) = read "TreeValue" world
| not ok = abort ("could not read TreeValue")
| otherwise = ( countnodes (case treevalue of (v::(Tree Int))= v) 0
, dynapply countleafs treevalue
)
where
dynapply :: Dynamic Dynamic -> Dynamic
dynapply (f::a -> b) (v::a) = dynamic (f v)
dynapply df dv = dynamic "incorrectly typed dynamic application"
countnodes Leaf nnodes = nnodes
countnodes (Node left right) nnodes = countnodes left (countnodes right (nnodes+1))
上記例から、ディスクに保存されるDynamicは、データだけでなくコード(未評価関数と関数適用)も保持できることが分かる。この情報はどのようにしてファイルに保存され、実行中のCLEANアプリケーションにどのように新規データと新規機能を提供するのだろうか?これを理解するには、CLEANアプリケーションの生成方法についてもう少し知らなければならない。
ダイナミックの実装
CELANアプリケーションは、インタプリタを経由して解釈されない。コンパイルされたマシンコードを使用して実行可能ファイルが生成される。Dynamicをディスクに保存し、それを再びディスクから取得するのは、単純にCLEANソースコードの(再)解釈によって行うことはできない。
(CLEANで書かれている)CLEANコンパイラは、CLEAN実装モジュール(.iclと.dclファイル)を、マシン非依存のabcコード(.abcファイル)にコンパイルする。CLEAN定義モジュールは、個別にプログラムされたCLEANモジュール間の型一貫性を検査するのに使用される。abcコードは、仮想抽象マシンであるabcマシン(Plasmeijer and Van Eekelen,1993参照)のマシン命令を保持する。abcコードは、CLEAN用に特別に設計された、一種のプラットフォーム非依存バイトコードである。コードジェネレータ(Code Generator)(CLEANシステムにおいて、Cで書かれているたった1つのアプリケーション)は、abcコードをプラットフォーム依存なマシンコード(Windowsでは.objファイル)に翻訳する。コードジェネレータは、Intel(Windows,Linux)、Motorola(Mac)とSparc(Unix)のようなプロセッサ用の様々なプラットフォームのコードを生成できる。静的リンカ(Static Linker)(CLEANで書かれている)は、1つのCLEANプログラムのオブジェクトモジュールの全てを連係して、1つのクリック可能な実行可能アプリケーション(Windowsでは.exeファイル)にする。以上のコンパイルスキームは、ダイナミックがアプリケーション内部で使用される場合にさえ使用できる。しかし、DynamicがFileや別のプログラムと通信するとすぐに、様々な実行時のサポートが必要であるので、これを準備する為、伝統的なコンパイルスキームは変更される。
変更後のコンパイルスキームでは、静的リンカは、アプリケーション(実際には、現在のところ.batファイルである)だけでなく、2つの追加的なファイルを生成する。1つは、コードレポジトリ(.libファイル)と呼ばれる。アプリケーションの全オブジェクトコードがここに集められる。もう1つのファイル(.typファイル)は、型定義を全て持つレポジトリである。レポジトリは、動的リンカ(Dynamic Linker)がアクセスするデータベースとして機能する。実行中のアプリケーションがプラグインのコードや検査されなければならない型を必要とする場合にはいつでも、動的リンカは、適切なレポジトリでそれを探す。CLEANプログラムが再コンパイルされる度に、新規のレポジトリが生成される。レポジトリを手動で消去してはならない。というのも、ディスクに保存されたDynamicが読込めなくなるかもしれないからである。非使用レポジトリを消去できる特別なカーベッジコレクタが提供されている。
動的I/Oを行うCLEANアプリケーションを起動すると、特別なリンカ(CLEANで書かれた動的リンカ)も起動される(未起動ならば)。動的リンカは、コンピュータ上のサーバアプリケーションである。これは、Dynamicの読書きを行う、実行中の全てのCLEANプログラムのサーバである。動的リンカは、静的リンカがコンパイル時に従来のCLEANプログラムに行うのと同じ方法で、実行時にアプリケーション又はプラグインを構築する。コードが一度連係されれば、効率性に関するペナルティは無い。
Dynamicが関数writeDynamicを使用してディスクに書かれる場合、2つ(!)のファイルが生成される。つまり、.dynファイルと.sysdynファイルである。.sysdynファイルは、実際の情報、つまりダイナミックのStringエンコーディングを保持する。このsysdynファイルは、動的リンカによって使用されるので、ユーザは触れてはならない。というのも、ディスクに保存されているDynamicが読込めなくなるかもしれないからである。提供される特別なガーベッジコレクタは、非使用.sysdynファイルも消去する。
ユーザーは、.sysdynファイルに保存される実際のダイナミックへの参照を持つ.dynファイルに触れ、使用できるのみである。.dynファイルは、"型付けされた"ファイルと見ることができる。その他のユーザーファイルのように処理できる。何の危険も無く、改名したり移動したり削除したりできる。
Dynamicがファイルに書込まれる場合、グラフとその型のエンコーディングがディスクに書込まれる。グラフが再び書込まれる場合に共有が維持されるという方法で、グラフがエンコードされる。保存されたグラフは、未評価関数を保持できる。ディスク上のDynamicでは、関数が、それに対応するコードレポジトリへの記号的ポインタによって表現される。ディスク上のダイナミックに格納される型は、型レポジトリに保存されるそれに対応する型定義を指し示す。
その型が承認されない限り、プラグインは嵌め込まれない。ディスクに保存されたDynamicが(他の)アプリケーションによって読込まれる場合、保存されたDynamicへのポインタのみがまずCLEANプログラムで使用される。アプリケーションにDynamicを使用する為に、まず、パターン照合でそれを調べなければならない。パターン照合では、Dynamicの型は、指定された型又は別のDynamicの型に単一化される。実行時型単一化が成功した場合、動的リンカは、関連する型の全ての型定義も同一であるかを検査する。その型情報は、必要な場合にそれに対応する型レポジトリから取得される。型が同一で、従来のパターン照合も成功した場合、それに対応する関数本体が評価できる。保存されたDynamicの評価が要請される場合にのみ、ディスク上のダイナミックにおいてエンコードされたグラフが、必要な限り再構築される(Dynamicの中に入れ子されるDynamicは遅延的に再構築される)。動的リンカは、Dynamicに保存される未評価関数に対応するコードで連係する。これは、コードレポジトリから取得され、実行中(!)のアプリケーションにプラグインされる。動的リンカが、コードジェネレータに、必要なイメージ構築の為に新規コードの生成(ジャストインタイムなコード生成)を命令する場合がある。動的リンカは、異なったアプリケーションで定義された同一のデータ型が、実行時には実際に同型であるとみなされるような方法で連係されることを保証しなければならない。
ダイナミックは、CLEAN 2.0の実験的な特徴である。現行版では、依然として多くの制限と限界が存在する。
- 次の型を閉包/開包できない。抽象データ型、一意性型、多重定義型。これに関しては目下作業中である。
First Uploaded : April 8, 2004
Last Modified : April 30, 2004
Back