値コンストラクタ式

型とパターンのページで色々な種類の値を紹介した。スカラ定数(整数、文字、アトム)、構造化された値(ペア、レコード、シーケンス、XML要素)、関数型の値(抽象)である。値自体が式であり、構造化された値を作る値コンストラクタも、式として機能する。

このページでは、CDuceにおけるその他の種類の式を紹介する。

パターンマッチ

CDuceにおける基礎的な操作は、パターンマッチである。

match e with
 | p1 -> e1
...
 | pn -> en

最初のの縦棒|は省略できる。eの評価結果と各パターンpiとマッチさせようとするという意味になる。最初にマッチしたパターンが、対応する右側の式を動かす。右側の式では、パターンによって束縛された変数を使うことができる。選言パターンと同様に、ファーストマッチポリシが適用されることに注意すること。

静的型システムにより、パターンマッチが網羅的であることが保証される。eに対して計算された型は、全てのパターンが受理する型の和の部分型でなければならない。

ローカル定義は、単一の分岐があるパターンマッチの簡易記法である。

let p = e1 in e2

は、次と等しい。

match e1 with p -> e2

パターンpが単一の捕獲変数でなくてもいいことに注意。

関数

抽象

関数式に対する一般形は、次のとおりである。

fun f (t1 -> s1; ...; tn -> sn)
 | p1 -> e1
...
 | pm -> em

最初の行は関数のインタフェースであり、残りが本体である。本体は、パターンマッチの形式になっている(したがって最初の縦棒|は省略できる)。

識別子fはオプションである。これは再帰関数を定義するときに便利である(関数本体がこの識別子を用いて関数自体を参照することができる)。

関数インタフェースは、関数の振る舞いにおける制約を指定する。すなわち、関数はtiという型の引数を受け取ると、結果が(もしあれば)si型でなければならないということである。型システムにより、各制約に対して本体を検査することで、この特性が保証されている。

関数は、標準のパターンマッチと全く同様に、引数を(値として)パターンマッチすることで動作する。実際に、インタフェースと本体の間に、x -> match xという行を、意味を変えずに追加することが常に可能である。

インタフェースが単一の制約のみを持つ場合、代替記法があり、幾つかの引数がある(つまり、引数がタプルである)ときは、簡単になる。

fun f (p1 : t1, ..., pn : tn) : s = e

(パターンが変数であるときに、コロンの間の空白が必須となることに注意。)は、次と等しい。

fun f ((t1,...,tn) -> s) (p1,...,pn) -> e

この構文でカリー化される関数を定義することも可能である。

fun f (p1 : t1, ..., pn : tn) (q1 : s1, ..., qm : sm) ... : s = e

は、次と厳密に等しい。

fun f ((t1,...,tn) -> (s1,...,sm) -> ... -> s) 
 (p1,...,pn) -> 
  fun ((s1,...,sm) -> ... -> s)
   (q1,...,qm) -> 
     ...
     e

関数をローカル束縛するための標準記法は、次のとおりである。

let f = fun g (...) ... in ...

ここで、fは関数の"外部"名であり、gは"内部"名(例えば関数が自分自身を再帰的に呼ぶ必要があるときに使われる)である。二つの名前が一致するとき(又は、内部名が必要ないとき)、簡略記法がある。

let fun f (...) ... in ...
let f (...) ... in ...

適用

関数を使うには、結局のところ引数を適用するしかない。記法は、関数と引数の単なる併置である。例:

(fun f (x : Int) : Int = x + 1) 10

11と評価される。静的型システムにより、アプリケーションが決して失敗しないことが保証される。

CDuceにおいて、関数型の"パターン"はないが、パターンにおいて関数型の型制約は使えることに注意。


fun (Any -> Int)
 | f & (Int -> Int) -> f 5 
 | x & Int -> x
 | _ -> 0

例外

次のコンストラクションは、例外を発生させる。

raise e

eの評価結果が例外の引数となる。

例外ハンドラで例外を捕捉できる

try e with
 | p1 -> e1
...
 | pn -> en

eの評価により例外が発生すると必ず、ハンドラはパターンに例外の引数をマッチさせようとする(ファーストマッチポリシに従う)。どのパターンもマッチしなければ、例外は伝播される。

MLと逆で、例外名がないことに注意すること。例外によってもたらされる情報は、引数だけである。結果として、プログラマの責任で、正しい例外を認識するのに十分な情報を引数に含ませることになる。例外ハンドラ中の分岐(`A,x) -> eでは、捕獲変数xについての静的型情報は何も与えられない(xの型はAnyとなる)ことにも注意すること。注意: 例外のサポートが、将来、MLのような名前付き例外にマッチするように変更される可能性がある。

レコード操作

レコードに対する操作には3種類がある。

算術演算子

整数に対する二項算術演算子には、+-*divmodがある。/は投影に使われ、除算には使われないことに注意すること。

演算子+-*は、単純な範囲の算法を用いて型付けされる。演算子div及びmodは、二つ目の引数の型が整数0を含んでいれば、コンパイル時に警告を出す。

Floatは、浮動小数点数を表現する。演算子float_of: String -> Floatがこの型の値を作るために提供されている。現在のところ、この型に対しては、ほかの演算子は提供されていない(が、浮動小数点数に対して動作するOCamlの関数を使うことができる)。

汎用比較、if-then-else

二項比較演算子(ブールを返す)には、=<<<=>>=>=がある。<がXML要素に使われていて、比較に使えないことに注意。

値が関数を含んでいれば、比較の意味は定義されない。そうでなければ、比較はCDuce値上の全順序を与える。全ての比較操作に対する結果の型はBoolである。ただし、引数が静的に異なるとわかる(型が排反である)等式を除く。この場合は、結果の型は単一の`falseとなる。

if-then-elseコンストラクションの標準形は、

if e1 then e2 else e3

で、次と等しい。

match e1 with `true -> e2 | `false -> e3

else節が必須であることに注意。

中置演算子||及び&&は、それぞれ論理和及び論理席を意味する。前置演算子notは論理否定を意味する。

上位強制

式が明確な型を持っていることを"忘れさせ"、上位型を与えることができる。

(e : t)

この式の型は、tであり、eはこの型を持っていなければならない(部分型を持っていてもよい)。この"上位強制"は、ローカルlet束縛と組み合わせることができる。

let p : t = e in ...

これは次と等しい。

let p = (e : t) in ...

上位強制により、より早期に型エラーを検知したり、よりうまくプログラムへのローカライズをしたり、より有益なメッセージを提供することができるようになることに注意すること。

CDuceは、動的な型検査コンストラクションも持っている。

(e :? t)
let p :? t = e in ...

eの評価の結果として得られた値が型tを持っていなければ、(Latin1型の)引数でマッチしなかった理由を説明する例外を発生させる。

シーケンス

連結演算子は@と書く。加えて、シーケンスのシーケンスを取って、その連結を返すflattenがある。

シーケンス上を反復するビルトインのコンストラクションが二つある。ともに、入力シーケンスの要素の位置を考慮に入れた、その静的な型によって与えられる非常に明確な型付けを持つ。mapコンストラクションは、次のとおりである。

map e with
 | p1 -> e1
...
 | pn -> en

パタンマッチと構文的に似ていることに注意せよ。実際に、mapはパターンマッチの形式を持ち、その分岐が入力列の入力列(eの評価結果)の各要素に順に適用される。意味は、入力シーケンスの各要素が、マッチにおける分岐の結果に置き換えられた、同じ長さのシーケンスを返すということである。

mapと逆に、transformコンストラクションは、異なる長さのシーケンスを返す。これは、各分岐に、単一の要素の代わりにシーケンスを返させることで実現されている。構文は、次のとおりである。

transform e with
 | p1 -> e1
...
 | pn -> en

transformの末尾に常に暗黙のデフォルトの分岐_ -> []があり、入力シーケンスのマッチしない要素が単に捨てられることを意味する。

mapは、各式ei[ ei ]に置き換えることで、transfotmを使ってシミュレートできることに注意すること。

逆に、transformは、flatten演算子を使うことでmapでシミュレートできる。実際に、transform e with ...flatten (map e with ... | _ -> [])と書き直すことができる。

XML特有のコンストラクション

XML文書のロード

load_xml : Latin1 -> AnyXmlビルトイン関数は、ローカルファイルシステム上のXML文書をパースする。引数はファイル名である。型AnyXmlの結果は、次のとおりである。

type AnyXml = <(Atom) (Record)>[ (AnyXml|Char)* ]

netclient又はcurlのサポートが利用できれば、XMLファイルをURLから取ってくることもできる。例: load_xml "http://..."。特別なスキームstring:は常にサポートされている。スキームに続く文字列は、そのとおりパースされる。

又、load_xml: Latin1 -> [Any*]ビルトイン関数もあり、HTML文書を寛大にパースする。

XML文書の清書

XML文書から文字列を生成するのに、二つのビルトイン関数を使うことができる。

print_xml: Any -> Latin1
print_xml_utf8: Any -> String

引数がXML文書でなければ、失敗する(これは静的には検査されない)。最初の演算子print_xmlは、ISO-8859-1でエンコードされたXML文書にダンプできるよう、文書を準備する。Latin1外のUnicode文字列は適宜エスケープされ、又、文書がISO-8859-1で表現できないタグ又は属性名を含んでいれば、この演算子は失敗する。二番目の演算子print_xml_utf8は、常に成功するが、UTF-8でエンコードされたファイルにダンプされるのに適した文字列を生成する。入力/出力に関する項にある、dump_to_file演算子の変種を参照のこと。

両方の場合とも、XMLの前置き"<?xml ...>"を含まない。

投影

投影はXML要素のシーケンスを受け取り、与えられた型の全ての子供の連結を返す。構文は、

e/t

であり、次と等しい。

transform e with <_>[ (x::t | _)* ] -> x

例えば、式[ <a>[ <x>"A" <y>"B" ] <b>[ <y>"C" <x>"D"] ] / <x>_は、[ <x>"A" <x>"D" ]と等しい。

属性を抽出する別の形式の投影がある。

e/@l

これは次と等しい。

transform e with <_ l=l>_ -> l

ドット記法を使って、一つのXML要素に対して属性の値を抽出できる。

# <a x=3>[].x;;
- : 3 = 3

XMLツリー上の反復

XML特有のコンストラクションに、xtransformであり、transformをXMLツリーへ一般化したものである。

xtransform e with
 | p1 -> e1
...
 | pn -> en

ここで、入力シーケンス中のXML要素がパターンにマッチしなければ、要素は複製される。ただし、変換は内容に対して再帰的に適用される。マッチしない、及びXML要素でない入力シーケンス中の要素は、そのまま複製される。

Unicode文字列

文字列は文字のシーケンスに過ぎないが、XMLを扱うときのその重要さを考慮して、標準的なダブルクォート記法を導入する。したがって、[ 'F' 'r' 'a' 'n' 'c,' 'e' ]"Franc,e"と書くことができる。ダブルクォートの中では、どんなChar型の値も使うことができる。ゆえに、Unicode文字に加えて、コードポイントで定義された文字\xh; \d;(h及びdをそれぞれ十六進数及び十進数の整数とする)、及びバックスラッシュでエスケープされた文字(\tタブ、\nニューライン、\rリターン、\\バックスラッシュ)を使うことができる。代わりに、値でない文字の式を使うことはできない。例えば、与えられたUnicodeコードポイントに対応する文字を返す(又は、存在しないコードポイントに対しては例外を発生させる)ビルトイン関数char_of_int : Int -> Charがあり、これは通常のシーケンス記法でしか使用できないので、"Franc,e""Fran"@[(char_of_int 231)]@"e"、及び"Fran\231;e"は等価な式となる。

文字列への、及び文字列からの変換

値の清書

ビルトイン関数string_of: Any -> Latin1は、CDuceのインタプリタ自体と同じ清書を用いて、いかなる値も文字列に変換するものである。

文字列からのアトムの生成、分解

ビルトイン関数split_atom: String -> String及びmake_atom: (String,String) -> Atomは、アトムと文字列のペア(名前空間、ローカル名)間の変換を行うものである。

文字列からの整数の生成

演算子int_ofは文字列を整数に変換する。文字列は十進(デフォルト)、又は十六進(0x又は0Xで始まっていれば)、八進(0o又は0Oで始まっていれば)、又は二進(0b又は0Bで始まっていれば)で読まれる。整数の十進表現でなければ、又は、十六進、八進、二進表現の場合は、整数が64ビットに収まらなければ、失敗する。引数が型[ '-'? '0'--'9'+ ] | ['-'? 'O'('b'|'B') '0'--'1'+ ] | ['-'? 'O'('o'|'O') '0'--'7'+ ] | ['-'? 'O'('x'|'X') ('0'--'9'|'a'--'f'|'A'--'F')+]を持つと証明できない場合は、型検査警告を出す。

整数からの文字列の生成

ビルトイン関数string_of: Any -> Latin1に加えて、そのコードポイントから文字を、したがって文字列を生成することもできる。バックスラッシュ(十六進のコードでは\x)とセミコロンで囲まれたコードによって、又はビルトイン関数char_of_int : Int -> Charを適用することによってのいずれかである。

入出力

文字の表示

標準出力に文字列を印字するには、ビルトイン関数print: Latin1 -> []又はprint_utf8: String -> []のいずれかを使うといい。

ファイルのロード

ファイルをCDuce文字列にロードするのに使えるビルトイン関数が二つある。

load_file: Latin1 -> Latin1
load_file_utf8: Latin1 -> String

前者はISO-8859-1でエンコードされたファイルをロードするが、後者はUTF-8でエンコードされたファイルをロードする。

netclinet又はcurlのサポートが利用可能であれば、URLからファイルをとってくることもできる。例: load_file "http://..."

ファイルのダンプ

CDuce文字列をファイルにダンプするのに使える演算子が二つある。

dump_to_file e1 e2
dump_to_file_utf8 e1 e2

前者はISO-8859-1でエンコードされたファイル(CDuce文字列が非Latin1文字を含んでいれば失敗する)を生成する。一方、後者はUTF-8でエンコードされたファイルを生成する。両方の場合において、最初の引数がファイル名で、二番目の引数がダンプする文字列である。

システム

外部コマンドの実行

定義済み関数systemは、外部コマンドを(/bin/shに渡して)実行し、標準出力と標準エラーチャンネルと終了コードを返す。systemの型は、次のとおりである。

Latin1 -> { stdout = Latin1; stderr = Latin1; 
             status = (`exited,Int) | (`stopped,Int) | (`signaled,Int) |}

プログラムの終了

定義済み関数exit: 0--255 -> Emptyはカレントプロセスを終了する。引数は終了コードである。

環境へのアクセス

ビルトイン関数getenv: Latin1 -> Latin1は、システム環境に環境変数を問い合わせる。引数が既存の変数を参照していなければ、関数は`Not_found例外を発生させる。

コマンドライン引数

ビルトイン関数argv: [] -> [ String* ]は、カレントプログラムに与えられたコマンドライン引数のシーケンスを返す。

名前空間

式の場所で、ローカル接頭辞・名前空間束縛を定義したり、又はローカルデフォルト名前空間を設定したりすることができる。

namespace p = "..." in e
namespace "..." in e

詳しくはXML 名前空間を参照のこと。

やむを得ない機能

コンストラクションref T eは、式eの結果によって初期化される参照を構築する。後で、リファレンスは、型Tのどんな値でも受け取ることができる。参照は、実際には型{ get = [] -> T ; set = T -> [] }の値である。

二つの糖衣構文が提供されており、参照の使用が容易になっている。

!e        ===  e.get []            取り出し
e1 := e2  ===  e1.set e2           割り当て

[]の式は、コマンドであると考えられ、別の式が続くることがよくある。順序付け演算子により、それに対する構文が与えられる。

e1 ; e2   ===  let [] = e1 in e2   順序付け

問い合わせ

CDuceでは、SQLのような問い合わせを実行するselect_from_where構文が与えられている。select式の一般形は、次のとおりである。

select e from
   p1 in e1,
   p2 in e2,
       :
   pn in en
where c

ただし、eを式、cをブール式、piをパターン、そしてeiをシーケンス式とする。

標準のSQL select式と同じように動く。違うのは、inキーワードの後の関係(タプルのシーケンス)が、ここでは汎用のシーケンスになることができ、inキーワードの前に、単なる捕獲変数の代わりに汎用のパターンを使うことができることである。したがって、結果は、cが満たされるという条件で、各パターンpiをシーケンスeiの各要素とマッチさせることの繰り返しが、eの自由変数を束縛した環境の下、eを計算して得られる、全ての値のシーケンスである。言い換えれば、結果の最初の要素は、p1e1の最初の要素に対してマッチさせ、p2e2の最初の要素に対してマッチさせ、…、そしてpnenの最初の要素に対してマッチさせることによって得られる環境の下、eを計算して得られる。結果の二番目の要素は、p1e1の最初の要素に対してマッチさせ、…、pnenの二番目の要素に対してマッチさせることによって得られる環境で、eを計算して得られる。そしてさらに続く。

正式には、上の選択式の意味は次のように定義される。

transform e1 with p1 ->
   transform e2 with p2 ->
         ...
       transform en with pn ->
          if c then  [e] else []

select式はネストしたtransform式の集合のように働く。transformではなくselectを使う利点は、問い合わせが、古典論理SQLの最適化技法を適用することで、自動的に最適化されるということである(この自動最適化は無効にできる)。

ビルトイン最適化器は、自由にあちこちへとブール条件を動かして、可能な限りすぐに評価する。条件がパターンによって捕獲される変数のいずれにも依存しなければ、警告が出る。