最近の更新 (Recent Changes)

2014-01-01
2013-01-04
2012-12-22
2012-12-15
2012-12-09

Wikiガイド(Guide)

サイドバー (Side Bar)

← 先頭のページに戻る

デカルト言語の基本的な機能のプログラム例

例題プログラムを実行するには、その内容を適当なファイルに記述してdescartesの引数として実行してください。

1. Hello, world

print述語でメッセージを表示します。

? <print "Hello, world!!">;

実行結果

 hello, world!!

 result --
   <print hello, world!!>
 -- true

2. 数の計算

let述語で整数を計算します。

? <let #x = 1+2+3>;

変数は頭に#を付けます。

letf述語は浮動小数点数を計算します。

? <letf #x = 1.23 + 2.34 + 3.456>;

letを省略すると、整数演算式として計算します。

? <#x = 1+2+3>;

letc述語は複素数の計算を行います。

? <letc #x = (1+i)/(1-i)>;

3. リストの追加

デカルト言語は、1階述語論理を基にしているため、prolog言語と多くの共通点を持ちます。

そこで、リストを連結するプログラムでデカルト言語とprolog言語を比較してみましょう。

デカルト言語では以下のようになります。

 <append #Z () #Z>;
 <append (#W : #Z1) (#W : #X1) #Y> <append #Z1 #X1 #Y>;
 ?<append #x (a b) (c d)>;

prolog(DEC-10 prolog)では以下のようになります。

 append(Z, [], Z).
 append( [W | Z1], [W | X1], Y) :- append(Z1, X1, Y).
 ? append(X, [a b], [c d]).

どちらの例でも、第2引数と第3引数のリストを連結し、第1引数に結果を返します。

違いがお分かりでしょうか。

1) 述語は<>で括られる。
2) 引数の区切りは空白を使う。
3) prologでは最後にピリオド"."を置くが、デカルト言語ではセミコロン";"を置く。
4) リストは[]ではなく()を使う。
5) リストを分割する"|"が、デカルト言語では":"である。
6) ヘッド部とボディ部の区切りにprologでは":-"を使うが、デカルト言語では何も無い。
7) デカルト言語では変数には"#"が付く。

この例では出てこないのですが、prologのカットオペレータ! は、デカルト言語では、<!>となります。

4. prologとの相違点

前項でデカルト言語とprologとの類似点について言及しましたが、ここでは相違点について述べます。

デカルト言語では、ボディ部に書けるものが述語の羅列に限られるprologとは異なり、任意のリストが書けることが大きく異なります。 リストの場合の実行順序は記述された左から順に実行されます。

<example #x> (<e1 #x> (<e2 #x #y> <e3 #y>) <e4 #y> ) <e5 #x>;

上記の例では、exampleが実行されると、e1, e2, e3, e4, e5の順に実行されます。

この記述の利点は、述語のつらなりをまとめて述語の引数に渡すようなメタな処理を行えることや、次に示す構文解析用の記号のために処理をまとめるために利用できることです。

構文解析のための記号として{}繰り返し、[]省略可能、|オア選択などが使えるのもprologとの大きな相違点です。

<example2 #a> <abc> { <def #a> | (<hij> <lkm>) } [ <nop> ] <end>;

上記の例では、example2が実行されると、abcが実行され、defか(hij lkm)のどちらか成功する処理が繰り返され、どちらも該当しなければループを抜けて、nopは成功しても失敗しても処理され、最後にendが実行されます。

5. 述語の値

デカルト言語では述語が実行され、評価された後は、true, false, unknownの3値を取ります。

一方、prologでは述語の評価された後に取る値には、trueとfalseがあります。

デカルト言語のunknownが、prologのfalseに相当します。この値のときは、他に解がないか探索が継続して実行されることになります。

trueはどちらでも同じです。

デカルト言語のfalseは、prologのfalseとは異なり、明に実行を失敗させ処理を打ち切るのに使います。

6. for文、foreach文による繰り返し処理

for述語を使うと、繰り返しのループ処理を実行できます。

 ?<for (#i 1 10) <print #i>>;

1から10までのループを実行します。

2重以上のループ処理も可能です。

 ?<for (#i 1 10)
   <for (#j 1 10)
     <print (#i #j)>
   >
 >;

foreach述語を使うと、引数のリストの要素を使って繰り返しのループ処理を実行できます。

 ? <foreach (#i (I think about it)
     <print "word : " #i>
 >;

上記では、I, think, about, itが順に表示されます。

7. 関数述語による計算

let, letf述語の引数は関数述語として評価されます。関数述語の返す関数値は第1引数です。

返り値の変数には、無名変数"_"を指定すると便利です。

 ? <letf #x = 1 + ::sys <cos _ 3.14>>;
 ::sys <cos _ 3.14>がcos関数の関数述語です。

関数述語を使用することにより、簡潔に再帰関数を書くことができます。

以下に階乗を計算する例を示します。


 <fact 1 1>;
 <fact #f #x>
   <#f = #x * <fact _ <_ = #x - 1>>>;


 ?<fact #v 10>;

 result --
   <fact 3628800 10>
 -- true

let文の中でlet文を呼び出すこともできます。


  ?<let #x = 100 + <let #y = 20*30>>;

 result --
   <let 700 = 100 + <let 600 = 20 * 30>>
 -- true

8. 関数述語による関数

let, letfは、数を返す関数しか使えませんが、func述語を使えば、さまざまなデータを引数とする関数を記述できます。

 ?<func #x this is a ::sys <geline _> ".">;

::sys <getline _>は、キーボードより1行入力する述語です。


以下の例では、関数型のLISP言語のようにリストを処理しています。

 ? <func _ ::sys<car _ ::sys<cdr _ (a b c)>>>;

結果は次のようになります。

 result --

   <func b ::sys <car b ::sys <cdr (b c) (a b c)>>>

 -- true

9. htmlの合成

func述語を使い、htmlファイルを合成するプログラムです。

テンプレートを用意し、その中の可変部分を述語や変数で埋め込み、関数述語として実行すると、目的のhtmlファイルが合成されます。

<html #html #title #body>	// ヘッド

  <func #html	// func関数述語。これより下の引数がテンプレート

       // #titleと#bodyが置き換えられる。

  (

"<HTML>

<HEAD>

<TITLE>" #title "</TITLE>

</HEAD>

<BODY>

  test html <BR>"

  #body "<BR>"

  ::sys<random  _ >

" </BODY>

</HTML>"

        )>;


ちょっと分かりにくいのですが、#title, #body, ::sys<random _>以外は全部文字列で""で囲まれています。デカルト言語では改行も文字列にそのまま含まれます。

つまり、func述語の引数では、文字列と変数と述語を混在させておくと、実行時に変数と述語は実行された結果が展開されます。述語は関数述語として扱われるので、第一引数の値が展開されるのです。

文字列はそのままで、結果としてすべてが連結されて返され、func述語の第一引数の変数に設定されます。

以下は結果です。

?<html #h "Hello World"   "This program is test."> ::sys <writenl #h>; // 実行
<HTML>
<HEAD>
<TITLE> Hello World </TITLE>
</HEAD>
<BODY>
  test html <BR> This program is test. <BR> 693663189 </BODY>
</HTML>)

引数として入力した、"Hello World"、 "This program is test." および乱数の部分が置き換えられているのが判るでしょうか。

10. EBNF記法

構文解析のためのEBNF記法が使えます。

拡張バッカス記法:EBNF(Extended Backus–Naur Form)記法とは、コンピュータのプログラミング言語の文法を規定するのに使われるメタ言語です。 元来は、プログラム言語のコンパイラやインタプリタの記述に用いられてきました。

デカルト言語では、EBNF記法を構文の基本的要素として採用しています。

EBNF記法を使うことにより、定められた文法に従って、プログラムやデータおよび英語や日本語などの自然言語でさえもある程度の自動的な解釈を可能とします。

以下には簡単な日本語の文章の解析例を示しましょう。

<>でくくられていない文字列または"文字列"は、入力された文字列と比較され一致するとtrueとなって処理が続行されます。 以下の例では、"田中", 佐藤, hniwaが、それに相当します。


 <名前> "田中";
 <名前> 佐藤;
 <名前> hniwa;

 <name #name>
   "私" "は"
   <名前> <GETTOKEN #x>
   "です"

   ["。"]
   ;


 ? ::sys<getline _ <name #name>>;

次のように入力すると名前だけが抽出されます。

 私はhniwaです。

 result --

 (< sys <getline 私はhniwaです。 <name hniwa>>>)

 -- true

#name変数に入力文の中の名前が取り出されています。

11. 文字列とのパターンマッチ

TOKEN述語を使用して、任意の文字列パターンにマッチするトークンを合成できます。(正規表現の代替となります。)


- 先頭が英字で2文字目から英数字の文字列

? ::sys<getline _
      <TOKEN #token  <A _> { <AN _> }>>;

getline述語はキーボードより1行入力し、TOKEN述語を呼び出しています。

A述語は英数字1文字と、AN述語は英数字1文字とマッチします。



- 数字列 (<NUM #token>と同等です)

? ::sys<getline _ 
        <TOKEN #token  { <N _> }>>;



- 大文字か数字の文字列
? ::sys<getline _ 
       <TOKEN #token { <RANGE  _  A  Z> | <N> } >>;

RANGE述語は指定された2つの引数の範囲にある文字とマッチします。上の例ではAからZの英大文字とマッチします。



- “DISK”+数字3桁

? ::sys<getline _ 
       <TOKEN #token  “DISK”  <N _>  ::sys<N _>  <N _>>>;



- ひらがな

? ::sys<getline _ 
       <TOKEN #token  { <RANGE  _  "ぁ"  "ん"> } >>;



- カタカナ

?  ::sys<getline _ 
       <TOKEN #token  { <RANGE  _  "ァ"  "ヺ"> } >>;





- 行の先頭がNo.で次に数字が続く

?  ::sys<getline _ 
       <TOKEN #token  <^> "No." <NUM #n> >>;



12. 文字列の構文解析

構文解析の対象はファイルあるいはgetline述語の入力だけではなく、syntax述語を使うことにより、任意の文字列に対しても実行できます。


- 先頭が英字で2文字目から英数字の文字列

 ? ::sys<syntax "var123b"
 
   <TOKEN #token  <A _> { <AN _> }>>;

syntax述語の第一引数の文字列"var123b"が、「先頭が英字で2文字目から英数字の文字列」か判定します。

 result --
   ::sys <syntax var123b <TOKEN var123b <A v> <loop (<AN Undef7>)>>>
 -- true

13. オブジェクト指向

オブジェクトは以下のような形式で定義します。

:: < オブジェクト名
  プログラム または
  inheirt 継承オブジェクト
>;

例として鳥、ペンギン、鷹のオブジェクト例を以下に示します。

::<bird
  <fly>;
  <walk>;
>;

::<penguin
  <fly>    <!><false>;
  <swim>;
  inherit bird;
>;

::<hawk
  inherit bird;
>;

オブジェクトの呼び出し方は、ライブラリの呼び出し方法と同じです。

以下を試してみましょう。

? ::bird <swim>;
? ::penguin <swim>;
? ::bird <walk>;
? ::penguin <walk>;
? ::bird <fly>;
? ::penguin <fly>;
? ::penguin <run>;
? ::hawk <fly>;
? ::hawk <walk>;
? ::hawk <swim>;

結果は以下のようになります。

result --
(<obj bird <swim>>)
-- unknown 鳥が泳ぐのか分からない

result --
(<obj penguin <swim>>)
-- true ペンギンは泳ぐ

result --
(<obj bird <walk>>)
-- true 鳥は歩く

esult --
(<obj penguin <walk>>)
-- true ペンギンは歩く

result --
(<obj bird <fly>>)
-- true 鳥は飛ぶ

result --
(<obj penguin <fly>>)
-- false ペンギンは飛ばない

esult --
(<obj penguin <run>>)
-- unknown ペンギンが走るか分からない

esult --
(<obj hawk <fly>>)
-- true 鷹は飛ぶ

esult --
(<obj hawk <walk>>)
-- true 鷹は歩く

esult --
(<obj hawk <swim>>)
-- unknown 鷹が泳ぐのかわからない

14. ユークリッドの互除法

ユークリッドの互除法を使い最大公約数を求めるプログラムを作ります。

<gcd 答 整数1 整数2>の形式で整数1と整数2の最大公約数を答に設定します。

ユークリッドの互除法については、書籍やWWW上で参照してください。

<gcd #x #x 0>;         // #xと0の最大公約数は、#xである。

<gcd #x #a #b>
  ::sys <compare #a >= #b>      // #aのほうが大きい場合
  <#c = #a % #b>                // letが省略されている
  <gcd #x #b #c>	                // 末尾再帰
  ;

<gcd #x #a #b>
  <#c = #b % #a>               // #bのほうが大きい場合
  <gcd #x #a #c>	               // 末尾再帰
  ;

?<gcd #x 511639100 258028360>;
result --
   (<gcd 20 511639100 258028360>)
-- true

15. フィボナッチ数

フィボナッチ数を求めるプログラムをつくります。

<fib 答 入力数>

入力数に対応するフィボナッチ数を答に設定します。

フィボナッチ数については、書籍やWWW上で参照してください。

このプログラムの特徴は、「通常の計算処理」を行った結果をキャッシュのようにプログラムに追加していくことです。

計算するほどキャッシュにたまる結果が増えて、より大きな数の計算を高速化することができます。

<fib 0 0>;			// 0の場合
<fib 1 1>;			// 1 の場合
<fib #result #n>		// 通常の計算処理
  <#n1=#n-1>
  <#n2=#n-2>
  <#result = <fib #nn1 #n1>+<fib #nn2 #n2>> // 再帰関数呼び出し
  ::sys <setArray fib #result #n>    // 結果をキャッシュに書き込む
  ;

?<fib #x 30>;
result --
(<fib 832040 30>)
-- true

16. quick sort

quick sort アルゴリズムを使いリストの中の要素をソートするプログラムを作ります。

<append #X () #X>;
<append (#A : #Z)  (#A : #X) #Y>
  <append #Z #X #Y>;

<qsort () ()>;			// ()をソートした結果は()
<qsort #sortedlist  (#x : #list) >
  <qsplit #x #list #l1 #l2>	// #list を #xより小さいか大きいかで
         // #l1,#l2に分ける
  <qsort #s1 #l1 >		// #l1をソート
  <qsort #s2 #l2 >		// #l2をソート
  <append #sortedlist  #s1 (#x : #s2) > // 結果を結合する
  ;

<qsplit _ () () ()>;
<qsplit #x (#y : #list) (#y : #l1) #l2>	// #y が#xより小さければ#l1に入れる
  ::sys <compare #y <= #x> 
  <qsplit #x #list #l1 #l2>;	// 末尾再帰
<qsplit #x (#y : #list) #l1 (#y : #l2)>	// #yが#xより大きいので#l2に入れる
  <qsplit #x #list #l1 #l2>;	// 末尾再帰

?<qsort #s  (11 12 3 7 1 2 0 -1 10 9 4 8 5 6) >;

result --
  (<qsort (-1 0 1 2 3 4 5 6 7 8 9 10 11 12)  (11 12 3 7 1 2 0 -1 10 9 4 8 5 6) >)
-- true