• トップページ
  • project
    • 全ての作品
    • 全てのタグ
  • blog
    • 全ての記事
    • 全てのタグ
  • tag
    • 全てのタグ
    • 全ての作品タグ
    • 全ての記事タグ
  • about
    • p進大好きサークル photo

      p進大好きサークル

      p進大好きサークルのHPです。

    • もっと読む
    • Twitter
    • pixiv
    • 巨大数Wiki

プログラミング言語Qp

最終更新日: 2020/05/26

トップページ 作品一覧

twitter pixiv yukicoder お題箱 マシュマロ

An English summary is available here.

ここではプログラミング言語\(\mathbb{Q}_p\)の仕様を自然言語ベースで曖昧に導入します。\(\mathbb{Q}_p\)は計算可能関数の定義を書きやすくかつ読みやすくするために

  1. 通常の数学の流儀に近い構文を採用すること
  2. C++コンパイラでコンパイルできること
  3. 定義文を自然言語に自動翻訳できること

のみを重視して開発したプログラミング言語です。なお現在サポートしている自動翻訳は日本語、英語、中国語、フランス語、ドイツ語です。またC++への翻訳を緩い制約下で可能にしているため、その制約の下でなら実際に実行することが可能です。ただしあくまで定義の書きやすさと読みやすさを目的としているので、実行速度にはこだわっていません。

また今後も開発を続けていく予定なので、こちらに記載された仕様は後に変更になる可能性が十分にあります。

0. コード例

構文の説明に入る前に、実際のコードを見てみましょう。

  
  USE( int , x );
  IMP( bool , r , x );
  SPEC( S , x , r );
  USE( S , y );

  USE( bool , b1 );
  USE( bool , b2 );
  
  USE( string , s1 );
  USE( string , s2 );

  IMP( int , g , s1 , s2 );
  SET( g , SYMBOL , STR( \\Gamma ) );
  SET( g , SEPARATOR , SYMBOLISE( g ) + LBIG + LPAREN + LMAT(c) + SPACE , SPACE + YY + SPACE , SPACE + RMAT + RBIG + RPAREN );

  IMP( int , f , s1 , s2 , b1 , y );
  SET( f , SEPARATOR , SYMBOLISE( f ) + SUB + LBRACE , RBRACE + SUP + LBRACE , RBRACE + LPAREN , COMMA , RPAREN );
  
  DEF( f ){

    PUT( b2 , s1 == s2 ) ,
      
      IF( b2 >> ( ( ! b1 && b2 ) || b2 ),

	  IF( ( b1 << ( b2 || b1 ) ) , PUT( x , 1 ) ) ,

	  // ここを通るのはバグ
	  IF( ! ( ( b1 && b1 ) ->* b1 ) , EXIT ) ,

	  IF( b2 , RETURN( x + y ) )
       
	  ) ,

      RETURN( y + g( s2 , s1 + s2 ) )
      
      };

  WRITE( S , JAPANESE , FANDOM , a.txt , TRUNC );
  
  DISPLAY( f , JAPANESE );
  WRITE( f , JAPANESE , FANDOM , a.txt , APP );
  

それぞれの行は以下のような意味を持ちます:

  • USEで定義に用いる変数の型と記号を宣言している。
  • IMPで定義に用いる演算子の戻り値の型と記号と引数を宣言している。
  • SPECで型の部分型を定義している。
  • SETとSYMBOLで翻訳時に表示される演算子記号の変更をしている。(オプション)
  • SETとSEPARATORで翻訳時に表示される演算子記法(通常の関数記法、ポーランド記法、中置記法、逆ポーランド記法、添字による記法、等)やセパレータには何を用いるかを設定している。(オプション)
  • DEFで定義を記述している。
  • PUTで引数以外の変数の値を定義している。
  • IFで場合分けを行っている。
  • EXITで異常終了を記述している。
  • RETURNで定義する演算子の戻り値を記述している。
  • DISPLAYで宣言の木構造を標準出力している。
  • WRITEで定義文の日本語訳をファイルに書き込んでいる。

下の画像は先程のコードを自動翻訳した日本語の文章です。上で説明した意味と整合的な翻訳になっていることが分かると思います。

1. 準備

細かい話に入る前に、導入方法や用語法や記法の説明をします。

1.1. 導入

無理に導入をしなくてもp進大好きbotにソースコードを送って下さればコンパイル結果や自動翻訳結果を返送しますが、以下が\(\mathbb{Q}_p\)の導入手順です。

  1. C++コンパイラ(なるべくバージョンが新しいGCC推奨)をインストールし、それを\(G\)と置く。
  2. 以下のurlからzipファイルをダウンロードし、それを解凍して得られるフォルダを\(r\)と置く。
    1. WindowsユーザーかつGCCユーザーの場合、\(\mathbb{Q}_p\)ライブラリからダウンロードする。
    2. 非Windowsユーザーまたは非GCCユーザーの場合、このレポジトリ全体を「Clone or Download」からダウンロードする。

以下が\(\mathbb{Q}_p\)のソースコードをコンパイルとリンクする手順です。

  1. \(\mathbb{Q}_p\)で許容される文字列を用意し、それを\(C\)と置く。
  2. 白紙のcppファイルを用意し、それを\(c\)と置く。\(c\)の存在するカレントディレクトリから\(r\)への相対パスからをRPと置く。例えば\(r\)がカレントディレクトリに存在する場合はRPが.であり、\(r\)がカレントディレクトリの親ディレクトリに存在する場合はRPが..であり、\(r\)がカレントディレクトリの子ディレクトリchildに存在する場合はRPが./childである。\(c\)にライブラリヘッダを以下のファイルをインクルードし、適宜改行をする。
    1. WindowsユーザーかつGCCユーザーの場合、\(c\)にRP/libqp.hppをインクルードする。
    2. 非Windowsユーザーまたは非GCCユーザーの場合、\(c\)にRP/cpp/Mathematics/Function/Computable/a.hppをインクルードする。
  3. \(c\)の末尾にint main(){ C; return 0; }というテキストを追加し、保存する。ただし\(c\)が日本語を含む場合は文字コードをShift-JISに設定する。
  4. \(c\)を\(G\)で文字コード-finput-charset=cp932 -fexec-charset=cp932と十分新しい標準ライブラリ(例えば-std=gnu++1z)を指定の下でコンパイルし、以下のようにライブラリをリンクすることで生成される実行ファイルを\(e\)と置く。
    1. WindowsユーザーかつGCCユーザーの場合、libqp.aをリンクする。
    2. 非Windowsユーザーまたは非GCCユーザーの場合、RP/cpp/Error/BreakPoint/a.cpp RP/cpp/Error/FaultInCoding/a.cpp RP/cpp/Error/IllegalAccess/a.cpp RP/cpp/Error/IllegalCall/a.cpp RP/cpp/Error/IllegalImput/a.cpp RP/cpp/Error/MismatchType/a.cpp RP/cpp/Error/Position/a.cpp RP/cpp/Error/Warning/a.cpp RP/cpp/Error/a.cpp RP/cpp/Utility/String/a.cpp RP/cpp/Mathematics/Arithmetic/Prime/a.cpp RP/cpp/Mathematics/Function/Computable/Expression/Primitive/bool/a.cpp RP/cpp/Mathematics/Function/Computable/Expression/Primitive/int/a.cpp RP/cpp/Mathematics/Function/Computable/Expression/Primitive/string/a.cpp RP/cpp/Mathematics/Function/Computable/Operator/Basic/bool/a.cpp RP/cpp/Mathematics/Function/Computable/Syntax/Translation/a.cpp RP/cpp/Mathematics/Function/Computable/Syntax/a.cpp RP/cpp/Mathematics/Function/Computable/Type/a.cppを全てコンパイルし、全ての中間生成ファイルをリンクする。もしくはお近くのPCショップでWindowsを購入するかGCCをインストールする。

\(C\)に記述するオプション次第で、\(e\)の実行時に\(C\)内の各種定義文を標準出力させたり自然言語に翻訳してファイルに書き出したりすることが出来ます。詳しくは「補助構文」の項を参照して下さい。

また上記の手順を少し変えることで、自然言語ではなくC++への翻訳も可能となります。詳しくはC++への翻訳の章をご覧下さい。

1.2. 変数の扱い

\(\mathbb{Q}_p\)における変数は、通常のプログラミング言語における変数と違って値の変更が不可能なものです。従って等号は全て「等しい」という関係でのみ使われ、代入による書き換えを表すことはありません。ポインタもありません。これらは通常の数学における変数に似せた仕様です。ただし通常の数学と違って量化子による変数の束縛を明示的に扱いません。とはいえ関数という概念の通常の数学における定義にはもちろん量化子による束縛が行われていますので、量化子が隠れているだけと思って下さい。

また演算子(関数や関係)の引数を含む全ての変数は、最初に宣言を必要とします。更に演算子の引数でない全ての変数は、その演算子の定義文内で最初に参照される際に値を定義される必要があります。これらの仕様は実用的でなく煩わしいかもしれませんが、「動く範囲の不明な変数を使う」とか「未定義の値を使う」といった巨大数の定義でありがちなミスを防ぐための厳格なものです。

1.3. 関数の扱い

\(\mathbb{Q}_p\)における関数は、通常のプログラミング言語における関数と違って変数の書き換えを行う機能はなく、また必ず戻り値を持つことが想定されています。これらは通常の数学における部分再帰的関数に似せた仕様です。ただし通常の数学と違って単に値を出力するだけでなく、標準出力を行うことも可能です。

また関数の宣言においては引数の指定を必要とします。引数自体も変数であり宣言が必要であるため、これにより関数は宣言の時点で定義域がどんな集合の部分集合であるかが確定します。これらの仕様は実用的でなく煩わしいかもしれませんが、「定義域の不明な関数を使う」とか「定義域外の値を関数に代入する」といった関数の定義でありがちなミスを防ぐための厳格なものです。

1.4. 階数

\(\mathbb{Q}_p\)で許容される文字列全体の集合は再帰的に定義されます。\(\mathbb{Q}_p\)で許容される文字列のいくつかには階数という自然数が定義されており、特定の階数を持つ\(\mathbb{Q}_p\)で許容される文字列の定義にはそれ以上の階数を持つ\(\mathbb{Q}_p\)で許容される文字列は使いません。

また\(\mathbb{Q}_p\)で許容される文字列の階数を集めて有限集合を考えることがありますが、その上限とは、その集合が空集合の時は\(0\)のことであり、空集合でない時はその最大値のことです。

1.5. 使用可能な文字

「文字列」と言った時に使用可能な文字を全て列挙するのは非常に面倒なのでしません。半角英数ならば問題がありませんが、部分的に漢字等を用いてもコンパイルは通ります。また例えば「コンマを含まない文字列」と言った時、コンマさえ含まなければヒエログリフでも架空の文字でも良いというわけではなく、本当はもっと色々と制約がありますが、面倒なので省略します。

1.6. 文字列の結合

文字列の列Aを,で結合して得られる文字列をこの記事ではConcatenate(A)と表します。例えばAが空列であればConcatenate(A)は空文字列となります。Aがただ\(1\)つの成分を持つならばConcatenate(A)はAの唯一の成分です。Aがちょうど\(2\)つの成分を持つならば、Aの第\(1\)成分をxと置き第\(2\)成分をyと置くと、Concatenate(A)は文字列x,yとなります。

Aに一切制限がない場合はConcatenate(A)からAを復元することが出来ないので、「Concatenate(A)に対し\(X\)という概念を(Aを用いて)\(Y\)と定める」のような文は\(X\)の定義を与えません。一方でAに十分強い制約(例えばいずれの成分も,を含まない等)を課している文脈ではConcatenate(A)からAを復元することが可能になることがあり、そういう場合には「Concatenate(A)に対し\(X\)という概念を(Aを用いて)\(Y\)と定める」のような文で\(X\)を定義することができます。

一般に「\(Z\)に対し、\(B\)を用いて\(X\)を\(Y\)と定める」という文は\(Z\)と\(B\)から\(Y\)が一意に定まりかつ\(Z\)から\(B\)が一意に定まる場合にのみ、\(Z\)のみに依存する概念としての\(X\)の定義を与えます。この記事においてもそれらの条件が満たされている場合にのみ、そのような構文を用いて定義していきます。

1.7. 文字列の位置関係

構文の説明の中に「~以降の文字列」という言葉遣いが現れますが、これは何となく意味が分かる概念でありながらもかなり曖昧な表現です。何故なら、これは2つの文字列の「位置関係」に関する述語であって2つの文字列そのものに関する述語ではなく、厳密に書こうとすると文字列の分割に関する述語として定義する必要があります。それを(書くのは簡単ですが)読むのは大変になると思うので、ここでは「~以降の文字列」という言葉遣いの厳密な定義はしません。

2. 構文

\(\mathbb{Q}_p\)で許容される文字列として、以下の分類を説明していきます。

  1. 型(type)
    1. 原始型(primitive type)
    2. 特殊型(specialised type)
    3. 配列型(array type)
    4. ネスト配列型(nested array type)
    5. 直積型(direct product type)
  2. 宣言(declaration)
    1. USE宣言(USE declaration)
    2. SUGAR宣言(SUGAR declaration)
    3. IMP宣言(IMP declaration)
  3. 表現(expression)
    1. 定数(constant symbol)
    2. 変数(variable symbol)
    3. 糖衣表現(sugar expression)
    4. 演算子表現(operator expression)
  4. 演算子(operator symbol)
    1. 関数(function symbol)
    2. 関係(relation symbol)
  5. 可変長引数演算子(variadic operator symbol)
    1. 可変長引数関数(variadic function symbol)
    2. 可変長引数関係(variadic relation symbol)
  6. 命令行(line)
    1. PUT文(PUT sentence)
    2. PRINT文(PRINT sentence)
    3. IF文(IF sentence)
    4. EXIT文(EXIT sentence)
    5. RETURN文(RETURN sentence)
  7. 定義文(definition)
    1. SPEC構成(SPEC construction)
    2. ARRAY構成(ARRAY construction)
    3. NEST構成(NEST construction)
    4. PROD構成(PROD construction)
    5. DEF文(DEF sentence)
  8. 補助構文(guide syntax)
    1. シンボル設定(symbol setting)
    2. セパレータ設定(separator setting)
    3. 全域性設定(totality setting)
    4. 宣言標準出力(declaration stdout)
    5. 翻訳出力(translation output)
  9. ソースコード(souce code)

これらの分類には重複があり、例えば定数は表現となり、表現は表現リストとなります。ソースコードである文字列が必ずしも全域な計算可能関数を定めるということではなく、ソースコードであることはあくまでコンパイルを通るための条件です。実行時に要請される追加の条件は「実行」の項で説明します。

2.1. 原始型

原始型またはprimitive typeとは、以下の4種類の文字列のことです;

  1. 文字列void
  2. 文字列int
  3. 文字列string
  4. 文字列bool

また基本型Tに対し、Tの階数という概念を\(0\)と定め、Tの基底型という概念をTと定めます。

2.2. SPEC構成

自然数\(R\)に対し、階数\(R+1\)のSPEC構成またはSPEC constructionとは、アルファベットと_のみからなる長さ\(1\)以上の文字列Tと\(R = \max \{R_1,R_2\}\)を満たす自然数\(R_1\)と\(R_2\)と階数\(R_1\)の変数xと階数\(R_2\)の関係rを用いてSPEC(T,x,r)と表される文字列のことです。SPEC(T,x,r);以降の文字列において、TはSPEC構成されていると言い、Tの階数という概念を\(R+1\)として定め、Tの基底型という概念をxの型の基底型と定めます。SPEC構成されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。特にNEST構成された文字列TをSPEC構成するSPEC構成は一意となるため、それをTの定義文と呼びます。(Tをrを満たすx全体の型としてspecialisationしたい時にSPEC(T,x,r);して下さい)

2.3. 特殊型

特殊型またはspecialised typeとは、アルファベットと_のみからなる長さ\(1\)以上の文字列であってSPEC構成されているもののことです。

2.4. ARRAY構成

自然数\(R\)に対し、階数\(R+1\)のARRAY構成またはARRAY constructionとは、アルファベットと_のみからなる長さ\(1\)以上の文字列Tと階数\(R\)の型Sを用いてARRAY(T,S)と表される文字列のことです。ARRAY(T,S);以降の文字列において、TはARRAY構成されていると言い、Tの階数という概念を\(R+1\)として定めます。ARRAY構成されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。特にARRAY構成された文字列TをARRAY構成するARRAY構成は一意となるため、それをTの定義文と呼びます。(TをSの元を成分に持つ配列全体の型として定義したい時にARRAY(T,S)して下さい。なお型の異なる空配列同士は区別されます)

ARRAY構成された文字列T1とT2の定義文がそれぞれARRAY(T1,S)とARRAY(T2,S)である時、「型の定義される文字列の型がT1であることを要請する場合に型T2でも良いものとする」という意味で型T1と型T2を同一視します。同一視される型のいずれか1つをこの記事内で表したい場合、それをS^ωと書きます。S^ω`はこの記事内だけの記法であり、それ自体は\(\mathbb{Q}_p\)で許容される文字列ではないことに注意して下さい。

Tの基底型という概念をSの基底型S0を用いてS0^ωと定めます。ただしこの定義ではTの基底型が文字列として一意ではありませんが、Tの基底型の曖昧さが問題を起こさないように構文が定義されます。

2.5. 配列型

配列型またはarray typeとは、アルファベットと_のみからなる長さ\(1\)以上の文字列であってARRAY構成されているもののことです。

2.6. NEST構成

自然数\(R\)に対し、階数\(R+1\)のNEST構成またはNEST constructionとは、アルファベットと_のみからなる長さ\(1\)以上の文字列Tと階数\(R\)の型Sを用いてNEST(T,S)と表される文字列のことです。NEST(T,S);以降の文字列において、TはNEST構成されていると言い、Tの階数という概念を\(R+1\)として定めます。ARRAY構成されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。特にNEST構成された文字列TをNEST構成するNEST構成は一意となるため、それをTの定義文と呼びます。(TはT自身の元を成分に持つ配列とSの元全体の型として定義したい時にNEST(T,S)して下さい)

NEST構成された文字列T1とT2の定義文がそれぞれNEST(T1,S)とNEST(T2,S)である時、「型の定義される文字列の型がT1であることを要請する場合に型T2でも良いものとする」という意味で型T1と型T2を同一視します。同一視される型のいずれか1つをこの記事内で表したい場合、それをS^{ω^ω}と書きます。S^{ω^ω}はこの記事内だけの記法であり、それ自体は\(\mathbb{Q}_p\)で許容される文字列ではないことに注意して下さい。

Tの基底型という概念をSの基底型S0を用いてS0^{ω^ω}と定めます。ただしこの定義ではTの基底型が文字列として一意ではありませんが、Tの基底型の曖昧さが問題を起こさないように構文が定義されます。

2.7. ネスト配列型

ネスト配列型またはnested array typeとは、アルファベットと_のみからなる長さ\(1\)以上の文字列であってNEST構成されているもののことです。

ただし型Sはネスト構成NEST(T,S)を持つネスト配列型Tを特殊化して得られる型ではないことに注意して下さい。特にSの基底型はTの基底型となりません。(特殊化は部分集合のような型を与えますが、逆に部分集合のような関係のある型同士は必ずしも特殊化で得られません)

2.8. PROD構成

自然数\(R\)に対し、階数\(R+1\)のPROD構成またはPROD constructionとは、アルファベットと_のみからなる長さ\(1\)以上の文字列Tと階数\(R\)を上限に持つ空文字列でも型でもない型リストTsを用いてPROD(Ts)と表される文字列のことです。PROD(T,Ts);以降の文字列において、TはPROD構成されていると言い、Tの階数という概念を\(R+1\)として定めます。PROD構成されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。特に特殊型TをPROD構成するPROD構成は一意となるため、それをTの定義文と呼びます。(TをTsのdirect productの元全体の型として構成したい時にPROD(T,Ts);して下さい)

PROD構成された文字列T1とT2の定義文がそれぞれPROD(T1,Ts)とPROD(T2,Ts)である時、「型の定義される文字列の型がT1であることを要請する場合に型T2でも良いものとする」という意味で型T1と型T2を同一視します。同一視される型のいずれか1つをこの記事内で表したい場合、それをΠ Tsと書きます。Π Ts`はこの記事内だけの記法であり、それ自体は\(\mathbb{Q}_p\)で許容される文字列ではないことに注意して下さい。

Tの基底型という概念をTsの各成分の基底型からなる型リストTs0を用いてΠ Ts0と定めます。ただしこの定義ではTの基底型が文字列として一意ではありませんが、Tの基底型の曖昧さが問題を起こさないように構文が定義されます。

2.9. 直積型

自然数\(R\)に対し、階数\(R+1\)の直積型またはdirect product typeとは、アルファベットと_のみからなる長さ\(1\)以上の文字列であってPROD構成されているもののことです。

2.10. 型

型またはtypeとは、以下の5種類の文字列のことです:

  1. 原始型
  2. 特殊型
  3. 直積型
  4. 配列型
  5. ネスト配列型

大雑把には型は命令文や表現の属性の1つです。例えばintは表現が整数に関係するという属性を表し、stringは表現が文字列に関係するという属性を表し、boolは表現が条件に関係するという属性を表すと考えてください。

2.11. 型リスト

自然数\(R\)に対し、階数\(R\)を上限に持つ型リストまたはtype listとは、\(0\)個以上の型の列ATであって階数の上限が\(R\)であるものを用いてConcatenate(AT)と表せる文字列のことです。

2.12. 定数

定数またはconstant symbolとは、以下の3種類の文字列のことです;

  1. 整数の符号付き十進法表記
  2. 文字列sであってエスケープされていない"や'を含まないものを用いてSTR( s )と表される文字列
  3. 文字列trueおよび文字列false

また定数cに対し、cの型という概念を上記の場合分けにおいてそれぞれint、string、boolと定め、cの階数という概念を\(0\)と定めます。

2.13. USE宣言

自然数\(R\)に対し、階数\(R+1\)のUSE宣言またはUSE declarationとは、アルファベットと_のみからなる長さ\(1\)以上の文字列xとvoidでない階数\(R\)の型Tを用いてUSE(T,x);と表される文字列のことです。USE(T,x);以降の文字列において、xはUSE宣言されていると言い、またxの型という概念をTと定め、xの階数という概念を\(R+1\)と定めます。USE宣言されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。(xをT型の変数としてuseしたい時にUSE(T,x);して下さい)

2.14. 変数

変数またはvariable symbolとは、USE宣言されている文字列のことです。

2.15. SUGAR宣言

自然数\(R\)に対し、階数\(R+1\)のSUGAR宣言またはSUGAR declarationとは、アルファベットと_のみからなる長さ\(1\)以上の文字列xと階数\(R\)の表現cを用いてSUGAR(x,c);と表される文字列のことです。SUGAR(x,c);以降の文字列において、xはSUGAR宣言されていると言い、またxの型という概念をcの型と定め、xの階数という概念を\(R+1\)と定め、xの実体という概念をcと定めます。SUGAR宣言されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。(xをc糖衣構文として使いたい時にSUGAR(x,c);して下さい)

2.16. 糖衣表現

糖衣表現またはsugar expressionとは、SUGAR宣言されている文字列のことです。(糖衣表現はその実体と同じ値を持ちますが、実体が変数であるとしても変数とはなりません)

2.17. 変数リスト

自然数\(R\)に対し、階数\(R\)を上限に持つ変数リストまたはvariable symbol listとは、\(0\)個以上の変数の列Axであって階数の上限が\(R\)であるものを用いてConcatenate(Ax)と表せる文字列のことです。Concatenate(Ax)の成分という概念をAxの成分として定めます。

2.18. 可変長変数リスト

自然数\(R\)に対し、階数\(R+1\)の可変長変数リストまたはvariadic variable symbol listとは、\(R = \max \{R_1,R_2\}\)を満たす自然数\(R_1\)と\(R_2\)と階数\(R_1\)を上限に持つ変数リストxと階数\(R_2\)の型\(V\)を用いてx,LDOTS(V)と表される文字列のことです。

2.19. IMP宣言

自然数\(R\)に対し、階数\(R+1\)のIMP宣言またはIMP declarationとは、アルファベットと_のみからなる長さ\(1\)以上の文字列fと\(R = \max \{R_1,R_2\}\)を満たす自然数\(R_1\)と\(R_2\)とvoidでない階数\(R_1\)の型Tと階数\(R_2\)を上限に持つ変数リストまたは可変長変数リストである文字列xを用いてIMP(T,f,x);と表される文字列のことです。IMP(T,f,x);以降の文字列において、fはIMP宣言されていると言い、fの戻り値の型という概念をTとして定め、fの引数という概念をxとして定め、fの階数という概念を\(R+1\)として定めます。IMP宣言されている文字列をSPEC構成、ARRAY構成、NEST構成、PROD構成、USE宣言、SUGAR宣言、IMP宣言することは出来ません。(fを戻り値がT型で引数がxである演算子としてimportしたい時にIMP(T,f,x);して下さい)

2.20. 宣言

宣言またはdeclarationとは、以下の2種類の文字列のことです;

  1. USE宣言
  2. SUGAR宣言
  3. IMP宣言

2.21. 演算子

演算子またはoperator symbolとは、アルファベットと_のみからなる長さ\(1\)以上の文字列であってIMP宣言されているもののうち、引数が変数リストであるもののことです。

演算子fに対し、fの引数の型リストがAでfの戻り値の型がRの時、fの高階型という概念を(A)->Rと定めます。

2.22. 関数

関数またはfunction symbolとは、戻り値の型がboolでない演算子のことです。

2.23. 関係

関係またはrelation symbolとは、戻り値の型がboolである演算子のことです。

2.24. 可変長引数演算子

可変長引数演算子またはvariadic operator symbolとは、アルファベットと_のみからなる長さ\(1\)以上の文字列であってIMP宣言されているもののうち、引数が可変長変数リストであるもののことです。

可変長引数演算子fに対し、fの引数の型リストがAでfの引数の可変長部の型がVでfの戻り値の型がRの時、fの高階型という概念を(A,LDOTS(V))->Rと定めます。

2.25. 可変長引数関数

可変長引数関数またはvariadic function symbolとは、戻り値の型がboolでない可変長引数演算子のことです。

2.26. 可変長引数関係

可変長引数関係またはvariadic relation symbolとは、戻り値の型がboolである可変長引数演算子のことです。

2.27. 演算子表現

自然数\(R\)に対し、階数\(R+1\)の演算子表現またはoperator expressionとは、\(R = \max \{R_1,R_2\}\)を満たす自然数\(R_1\)と\(R_2\)と以下の3条件のいずれかを満たす文字列fとaを用いてf(a)と表される文字列のことで、f(a)の型という概念をfの戻り値の型として定め、f(a)の階数という概念を\(R+1\)と定めます:

  1. fは階数\(R_1\)の演算子であり、aは階数\(R_2\)を上限に持つ表現リストであり、かつaの型リストとfの引数の型リストが等しい。
  2. fは階数\(R_1\)の可変長引数演算子であり、aは階数\(R_2\)を上限に持つ表現リストaであり、aの型リストがxの型リストTsと全ての成分がfの引数の可変長部の型である列Vsを用いてTs,Concatenate(Vs)と表せる。
  3. fは階数\(R_1\)の可変長引数演算子であり、aは階数\(R_2\)を上限に持つ可変長表現リストaであり、aの型リストがxの型リストTsと全ての成分がfの引数の可変長部の型である列Vsを用いてTs,Concatenate(Vs)と表せ、aの可変長部の型がfの引数の可変長部の型と等しい。

2.28. 表現

表現またはexpressionとは、定数または変数または糖衣表現または演算子表現である文字列のことです。

2.29. 表現リスト

自然数\(R\)に対し、階数\(R\)を上限に持つ表現リストまたはexpression listとは、\(0\)個以上の表現の列Aeであって階数の上限が\(R\)であるものを用いてConcatenate(Ae)と表せる文字列のことです。Concatenate(Ae)の型リストという概念をsの成分である表現の型の列ATを用いてConcatenate(AT)と表せる文字列として定めます。

2.30. 可変長表現リスト

自然数\(R\)に対し、階数\(R+1\)を上限に持つ可変長表現リストまたはvariadic expression listとは、\(R = \max \{R_1,R_2\}\)を満たす自然数\(R_1\)と\(R_2\)と階数\(R_1\)を上限に持つ表現リストaと階数\(R_2\)の型Vを用いてa,LDOTS(V)と表せる文字列のことです。a,LDOTS(V)の型リストという概念をaの型リストとして定め、a,LDOTS(V)の可変長部の型という概念をVと定めます。

2.31. PUT文

PUT文またはPUT sentenceとは、変数xと以下の2条件のいずれかを満たす文字列eを用いてPUT(x,e)と表される文字列のことです:

  1. eはxと基底型Tの型の等しい表現である。(変数xをeの糖衣構文としてputしたい時にPUT(x,e)して下さい)
  2. xの型Tはnatかstringのいずれかであり、eは基底型Tの型の変数nと基底型Tの型の表現mと基底型boolの型の表現bを用いてPUT(x,UNIQUE(n,m,b))と表せる。(変数xを「n <= mとbを満たす唯一のnの糖衣構文としてputしたい時にPUT(x,UNIQUE(n,m,b))して下さい)
  3. xの型はnatであり、eはnat型の変数nと基底型boolの型のbを用いてPUT(x,MIN(n,m,b))と表せる。(変数xを「bを満たす最小のnの糖衣構文としてputしたい時にPUT(x,MIN(n,b))して下さい)
  4. xの型はnatであり、eはnat型の変数nと基底型intの型の表現mと基底型boolの型のbを用いてPUT(x,MAX(n,m,b))と表せる。(変数xを「n <= mとbを満たす最大のnの糖衣構文としてputしたい時にPUT(x,MAX(n,m,b))して下さい)

PUT文PUT(x,e)をxのPUT文と呼び、PUT(x,e)の型という概念をvoidとして定めます。

2.32. PRINT文

PRINT文またはPRINT sentenceとは、表現リストaeを用いてPRINT(ae)と表される文字列のことで、PRINT(ae)の型という概念をvoidとして定めます。(aeの成分を標準出力したい時にPRINT(ae)して下さい)

2.33. EXIT文

EXIT文またはEXIT sentenceとは、文字列EXITのことで、EXITの型という概念をvoidとして定めます。(DEF文において処理を異常終了させたい時にEXITして下さい)

2.34. RETURN文

RETURN文またはRETURN sentenceとは、表現eを用いてRETURN(e)と表される文字列のことで、RETURN(e)の型という概念をeの型として定めます。(DEF文においてeを出力したい時にRETURN(e)して下さい)

2.35. 命令行

命令行、命令行リストおよびIF文という概念をという概念を以下のように同時に再帰的に定義します:

  1. 命令行またはlineとは、以下の5種類の文字列のことです:
    1. PUT文
    2. PRINT文
    3. IF文
    4. EXIT文
    5. RETURN文
  2. 命令行リストまたはline listとは、以下の2条件を満たす\(1\)個以上の命令行の列ALと型Tを用いてConcatenate(AL)と表せる文字列のことで、Concatenate(AL)の型という概念をTとして定めます。
    1. ALの成分である命令行の型はTまたはvoidである。
    2. ALはT型の命令行を少なくとも\(1\)個成分に持つ。
  3. IF文またはIF sentenceとは、以下の2条件を満たすbool型の表現bと命令行リストaLを用いてIF(b,aL)と表される文字列のことで、IF(b,aL)の型という概念をaLの型として定めます。

2.36. DEF文

DEF文またはDEF sentenceとは、演算子fと命令行リストaLであってfの戻り値の型とaLの型は等しいものを用いてDEF(f){aL}と表される文字列のことで、DEF(f){aL}の型という概念をaLの型として定め、DEF(f){aL}の定める演算子という概念をfとして定めます。DEF(f){aL};以降の文字列において、fは実装されていると言います。実装されている演算子を実装することはできません。特に演算子fを実装するDEF文は一意となるため、それをfの定義文と呼びます。

ちなみにDEF文内に;が現れるうるのはstring型の定数内のみです。どこかに;を書くべきかどうか迷ったら、それがDEF文の内側か外側かで判断しましょう。

2.37. 定義文

定義文またはdefinitionとは、以下の5種類の文字列のことです:

  1. SPEC構成
  2. ARRAY構成
  3. NEST構成
  4. PROD構成
  5. DEF文

2.38. シンボル設定

シンボル設定またはsymbol settingとは、以下の2種類の文字列のことです:

  1. 変数または演算子である文字列xとコンマ記号を含まない文字列sを用いてSET(x,SYMBOL,s);と表される文字列。(翻訳時にxをエスケープされたsのMathJaxでの表示に変更して出力したい時にSET(x,SYMBOL,s);して下さい)
  2. 文字列sを用いてSET(x,SYMBOL,PLAIN(s));と表される文字列。(翻訳時に変数または演算子である文字列xをエスケープされたsのプレインテキスト化に変更して出力したい時にSET(x,SYMBOL,PLAIN(s));して下さい)
  3. 変数または演算子である文字列xを用いてROMANISE(x);と表される文字列。(翻訳時にxをプレインテキスト化して出力したい時にDEF文より前にROMANISE(x);して下さい)

2.39. セパレータ設定

セパレータ設定またはseparator settingとは、演算子fとセパレータリストasを用いてSET(f,SEPARATOR,as);と表せる文字列のことです。(演算子のセパレータを設定したい時にDEF文より前にSET(f,SEPARATOR,as);して下さい。)

セパレータリストまたはseparator listとは、\(1\)個以上のセパレータの列Asを用いてConcatenate(As)と表せる文字列のことです。

セパレータまたはseparatorとは、以下のように再帰的に定義される文字列の属性です:

  1. 表現または演算子xに対し、文字列SYMBOLISE(x)(記号x)はセパレータである。
  2. 文字列EMPTY(空文字列)はセパレータである。
  3. 文字列ESCAPE(直後の文字のエスケープ)はセパレータである。
  4. 文字列SPACE(半角スペース)はセパレータである。
  5. 文字列SUB(下付き指示)はセパレータである。
  6. 文字列SUP(上付き指示)はセパレータである。
  7. 文字列LPAREN(左カッコ)はセパレータである。
  8. 文字列RPAREN(右カッコ)はセパレータである。
  9. 文字列LBRACE(左中カッコ)はセパレータである。
  10. 文字列RBRACE(右中カッコ)はセパレータである。
  11. 文字列LBRACK(左四角カッコ)はセパレータである。
  12. 文字列RBRACK(右四角カッコ)はセパレータである。
  13. 文字列LANGLE(左三角カッコ)はセパレータである。
  14. 文字列RANGLE(右三角カッコ)はセパレータである。
  15. 文字列VERT(縦線)はセパレータである。
  16. 文字列VVERT(二重縦線)はセパレータである。
  17. 文字列FRAC(分数用の横線)はセパレータである。
  18. 文字列LBIG(左カッコ等の拡大指示)はセパレータである。
  19. 文字列MBIG(カッコ内の縦線等の拡大指示)はセパレータである。
  20. 文字列RBIG(右カッコ等の拡大指示)はセパレータである。
  21. 文字列PERIOD(ピリオド)はセパレータである。
  22. 文字列COMMA(コンマ)はセパレータである。
  23. 文字列COLON(コロン)はセパレータである。
  24. 文字列SCOLON(セミコロン)はセパレータである。
  25. 文字列ATMARK(アットマーク)はセパレータである。
  26. \(1\)個以上の文字l, c , r , |のみからなる文字列sに対し、文字列LMAT(s)(行列開始位置)はセパレータである。
  27. 文字列RMAT(行列終端位置)はセパレータである。
  28. 文字列ET(列区切り位置)はセパレータである。
  29. 文字列YY(行区切り位置)はセパレータである。
  30. 文字列HLINE(行区切り用の横線)はセパレータである。
  31. 文字列LVEC(ベクトル開始位置)はセパレータである。
  32. 文字列RVEC(ベクトル終端位置)はセパレータである。
  33. 文字列XとYがセパレータならば、それらを+で結合した文字列はセパレータである。

2.40. 全域性設定

全域性設定またはtotality settingとは、演算子fと全域性cを用いてSET(f,TOTALITY,c);と表せる文字列のことです。

全域性またはtotalityとは、以下の3種類の文字列のことです。

  • PARTIAL
  • TOTAL
  • PRIMITIVE

演算子fの定義文を翻訳出力する際に、部分関数であることを明記したければSET(f,TOTALITY,PARTIAL);をし、全域関数であることを明記したければSET(f,TOTALITY,TOTAL);をし、原始再帰関数であることを明記したければSET(f,TOTALITY,PRIMITIVE);をして下さい。

この機能はあくまで翻訳出力時の出力内容に影響を与えるためだけのもので、計算可能性設定に反するような定義文を書いてしまったとしてもコンパイルエラーにはなりません。特に、全域性を主張させることは出来ても実際に全域かどうか(無限ループを生じさせないかどうか)はコンパイルによって判定することは出来ません。

2.41. 宣言標準出力

宣言標準出力またはdeclaration stdoutとは、型または表現または演算子である文字列sと対応言語Lを用いてDISPLAY(s,L);と表せる文字列のことです。(sの宣言の木構造をL語の説明付きで標準出力したい時にDISPLAY(s,L);して下さい)

対応言語またはsupported languageとは、以下の5種類の文字列のことです::

  • JAPANESE
  • ENGLISH
  • CHINESE
  • FRENCH
  • GERMAN

2.42. 翻訳出力

翻訳出力またはtranslation outputとは、型または糖衣表現または定義文を持つ演算子である文字列sと対応言語Lと出力形式Sとコンマを含まない文字列FとオープンモードMを用いてWRITE(s,L,S,F,M);と表せる文字列のことです。(sの定義文のL語訳をS仕様でカレントディレクトリからの相対パスがFのファイルにオープンモードMで書き込みたい時にWRITE(s,L,S,F,M);して下さい)

出力形式またはoutput styleとは、以下の2種類の文字列のことです:

  • FANDOM
  • JEKYLL

オープンモードまたはopen modeとは、以下の2種類の文字列のことです:

  • APP
  • TRUNC

2.43. 補助構文

補助構文またはguide syntaxとは、以下の5種類の文字列のことです:

  1. シンボル設定
  2. セパレータ設定
  3. 全域性設定
  4. 宣言標準出力
  5. 翻訳出力

これらは\(\mathbb{Q}_p\)の演算子の計算結果に一切影響を与えず、定義文の出力にのみ影響を与えます。

2.44. ソースコード

\(\mathbb{Q}_p\)のソースコードとは、宣言と定義文と補助構文のみからなる文字列の列の結合で表せる文字列のことです。

3.コンパイル

\(\mathbb{Q}_p\)のソースコードCに対して、「導入」の項で紹介した方法でint main(){C; return 0;}というコードをC++コンパイラでコンパイルすることが出来ます。このコードが正常にコンパイルできることはC自身が\(\mathbb{Q}_p\)のソースコードであるための必要条件に近い条件であって十分条件ではありません。例えばCがint x = 0という文字列の場合、Cは\(\mathbb{Q}_p\)のソースコードではありませんがint main(){C; return 0;}というコードは正常にコンパイルすることができます。

\(\mathbb{Q}_p\)のソースコードでない文字列の多くがコンパイルエラーとなりますので、\(\mathbb{Q}_p\)のソースコードのつもりで書いた文字列が実際に\(\mathbb{Q}_p\)のソースコードであるか否かの判定には十分機能すると思います。

ライブラリを導入するのが面倒という人はp進大好きbotにコードを渡して下さればコンパイルエラーの有無を返答致します。

3.1. 予約語

USE等の上記で説明されている文字列以外に、予め機能が定義されている文字列が存在します。例えばC++の予約語は\(\mathbb{Q}_p\)でも予約語となります。そうしないと、C++コンパイラで正常にコンパイル出来ないからです。C++の予約語以外の\(\mathbb{Q}_p\)の予約語は列挙するのが面倒なので、気にしないで下さい。

3.2. 糖衣構文

C++コンパイラに内蔵されているプリプロセッサを使うので、#defineにより糖衣構文を導入することが出来ます。従ってC++の定義済みマクロは定義せずに用いることが出来ますが、一方でC++の定義済みマクロと衝突するマクロを定義することは出来ません。

3.3 定義済み型

\(\mathbb{Q}_p\)にはいくつかの型が最初から定義されています。

  • int型の変数nを用いてSPEC(nat,n,NONNEG)と定義される、非負整数全体の集合に対応する型nat。

ただしNONNEGは後で説明する定義済み演算子です。

3.4 定義済み演算子

\(\mathbb{Q}_p\)にはいくつかの演算子が最初から定義されており、IMP宣言せずに用いることが出来ます。

3.4.1 糖衣構文付き演算子

加法や不等号や論理和などの基本的な以下の演算子はユーザー定義の演算子と異なり、中置記法等の特殊な記法を糖衣構文に持ちます:

演算子 意味 高階型 特殊記法 備考
PLUS 加算演算子 原始型または配列型またはネスト配列型である型 T に対する (T,LDOTS(T))->T 中置記法 + ただし string 型に対する + は文字列の結合に対応し、配列型やネスト配列型にする + は配列の結合に対応します。
TIMES 乗算演算子 int または bool である型 T に対する (T,LDOTS(T))->T 中置記法 *
MINUS 減算演算子 (int,int)->int 中置記法 -
SLASH 除算演算子 (int,int)->int 中置記法 /
MOD 剰余演算子 (int,int)->int 中置記法 %
POWER 冪演算子 (int,int)->int 中置記法 ^
NEG 否定演算子 (bool)->bool ポーランド記法 !
LAND 論理積演算子 (bool,LDOTS(bool))->bool 中置記法 && と *
LOR 論理和演算子 (bool,LDOTS(bool))->bool 中置記法 || と +
TO 含意演算子 (bool,bool)->bool 中置記法 >> と ->* 本当は TO の中置記法に -> を採用したいのですが、 -> はC++においてオーバーロードが制限されているため、C++コンパイラでコンパイルできるようにするために断念しました。
OT 逆含意演算子 (bool,bool)->bool 中置記法 <<
EQUIV 同値演算子 (bool,bool)->bool 中置記法 == 本当は EQUIV の中置記法に <=> を採用したいのですが、 <=> はC++においてオーバーロードが制限されているため、C++コンパイラでコンパイルできるようにするために断念しました。
EQ 等号演算子 型 T に対する (T,T)->bool 中置記法 == 中置記法 == は通常の等号のように a == b == c と並列することが出来ません。
NEQ 不等号演算子
(ノットイコール)
型 T に対する (T,T)->bool 中置記法 !=
LEQ 不等号演算子
(小なりイコール)
(int,int)->bool 中置記法 <=
GEQ 不等号演算子
(大なりイコール)
(int,int)->bool 中置記法 >=
LNEQ 不等号演算子
(小なり)
(int,int)->bool 中置記法 <
GNEQ 不等号演算子
(大なり)
(int,int)->bool 中置記法 >

3.4.2 その他の基本演算子

特殊な糖衣構文は持たないですが基本的な以下の演算子を使うことが出来ます:

演算子 高階型 説明
NONNEG (int)->bool n に対し n >= 0 を返します。
LENGTH (string)->nat
(T^ω)->nat
(T^{ω^ω})->nat
s に対し s の長さを返します。ただし DENESTABLE( a ) を満たす T^{ω^ω} 型の表現 a に対する LENGTH(a) は 0 と定義されるのではなく未定義です。
ENTRY (string,nat)->nat
(T^ω,nat)->T
(T^{ω^ω},nat)->T^{ω^ω}
(a,n) に対し a の第 1+n 番目の成分を返します。
INISEG (string,nat)->string
(T^ω,nat)->T^ω
(T^{ω^ω},nat)->T^{ω^ω}
(s,n) に対し s の始切片であって長さが n であるものを返します。
FINSEG (string,nat)->string
(T^ω,nat)->T^ω
(T^{ω^ω},nat)->T^{ω^ω}
(s,n) に対し s の終切片であって長さが n であるものを返します。
WRAP (T)->T^ω x に対し (x) を返します。
NWRAP (T^{ω^ω})->T^{ω^ω} a に対し (a) を返します。
ENNEST (T)->T^{ω^ω} x に対し x 自身を返します。
DENEST (T^{ω^ω})->T a に対し a 自身を返します。
DENESTABLE (T^{ω^ω})->bool a に対し a が T^{ω^ω} 型の元の配列でないか否かを返します。例えば T 型の表現 x を用いて ENNEST(x) や ENTRY(WRAP(x),0) と表せる時は DENESTABLE( a ) は true と評価されます。
NESTIFY (T^ω)->T^{ω^ω}
T^{ω^ω}->T^{ω^ω}
a に対し a 自身を返します。
PROJ < i > 非負整数 i に対する Π(T0,…,Tn)->Ti a に対し a の第 1+i 成分を返します。
TUPLE (T0,…,Tn)->Π(T0,…,Tn) a に対し a 自身を返します。

3.4.3 メタ演算子

既存の演算子から別の演算子を定義するために以下のマクロを使うことが出来ます:

マクロ 説明
^ 高階型 (T)->T の演算子 f と nat 型の表現 n に対し、 f の n 回合成に対応する高階型 (T)->T の関数 f^n を生成します。
CURRY 高階型 (T1,T2)->T3 の演算子 f と T1 型の表現 x に対し、 y に対し f(x,y) を返す高階型 (T2)->T3 の関数 CURRY(f,x) を生成します。デフォルトの CURRY(f,x)(y) は翻訳時にCurry(f,x)(y)と表示され、オプション引数付きの CURRY(f,x,EMPTY)(y) では翻訳時にf(x,y)と表示され、オプション引数付きの CURRY(f,x,SUB)(y) では翻訳時にfx(y)と表示されます。 CURRY(f,x,EMPTY)(y) は自然言語への翻訳時に関数としては引数1の関数にyを代入したものとして表示されないことになるので、引数1の関数に対するマクロである ^ と組み合わせて (CURRY(f,x)^2)(y) とすると自然言語への翻訳が意図しないものとなります。自然言語への翻訳を意図している場合は代わりに (CURRY(f,x)^2)(y) や (CURRY(f,x,SUB)^2)(y) を使って下さい。
ARRAYISE 高階型 (nat)->T の演算子 f と nat 型の変数 n に対し、 n に対し (f(0),…,f(n-1)) を返す高階型 (nat)->T^ω の関数 ARRAYISE(f,n) を生成します。ちなみに f の引数である変数が他の変数と被っている場合、自然言語への翻訳時に (f(n))_{n=0}^{n-1}} のような不適切な訳が出力されます。自然言語訳をしたい場合は f の引数が ARRAYISE(f) を用いるDEF文内の他の表現と衝突しないように注意しましょう。
EXISTS nat または string である型 T0 に対し、 T0 型の変数 x と高階型 (T0,T1,…,Tn)->bool の関係 r に対し、 (x1,…,xn) に対し x0 <= x && r(x0,x1,…,xn) を満たす x0 の存在性を返す高階型 (T1,…,Tn)->bool の関係 EXISTS(N,r) を生成します。
UEXISTS nat または string である型 T0 に対し、 T0 型の変数 x と高階型 (T0,T1,…,Tn)->bool の関係 r に対し、 (x1,…,xn) に対し x0 <= x && r(x0,x1,…,xn) を満たす x0 の一意存在性を返す高階型 (T1,…,T_n)->bool の関係 UEXISTS(n,r) を生成します。

3.4.4 ライブラリQpBasic

BASIC;と書くことで、追加のライブラリであるQpBasicに登録されている以下の演算子を使うことが出来ます:

演算子 高階型 説明
Arrow (nat,nat,nat)->nat (a,c,b) に対しハイパー演算子で a ↑^c b を返します。
Chain (nat,LDOTS(nat))->nat (a,…) に対しチェーン表記で a → … を返します。
N (string,nat,LDOTS(nat))->nat (v,n,…) に対しその時点で定義されているN原始 Nv(…)[n] を返します。

3.5. 無視される文字列

半角空白か改行が(か)か,か;の前後どちらかに\(1\)個以上ある場合、それらは全て無視されます。連続した;は1つの;と同等に処理されます。コメントアウトされた文字列も無視されます。C++のコンパイラを使うので当然ですね。

3.6. C++による拡張

C++コンパイラがC++のコードも許容することを逆に利用して、C++を部分的に組み込むように\(\mathbb{Q}_p\)を拡張することが出来ます。例えば\(\mathbb{Q}_p\)におけるint型の定数は1230のような整数の符号付き十進法表記しか許されていませんでしたが、C++のコードで定義された関数int f(int n)を用いてf(567)などとしたものも\(\mathbb{Q}_p\)の定数として使うことができます。例えば変数xに対するPUT(x,f(f(32)))等は問題なくコンパイル出来ます。

3.7. 自然言語への翻訳

\(\mathbb{Q}_p\)のソースコードCとCに含まれるvoidでない型またはDEF文の定める演算子である文字列sと対応言語Lと出力形式Sとコンマを含まない文字列FとオープンモードMに対し、導入の章で述べた手順でint main(){C; WRITE(s,L,S,F,M); return 0;}というコードをコンパイルして実行すると、sが糖衣表現または演算子の時は標準出力にsの定義文の構文木を出力し、いずれの場合もカレントディレクトリからの相対パスがFのファイルに定義文の自然言語訳を書き込みます。

  • パラメータLにより自然言語を指定します。
    • LがJAPANESEの時、対応する言語は日本語です。
    • LがENGLISHの時、対応する言語は英語です。
    • LがCHINESEの時、対応する言語は中国です。ただしエディタが簡体字に対応していなかったため、繁体字となります。
    • LがFRENCHの時、対応する言語はフランス語です。
    • LがGERMANの時、対応する言語はドイツ語です。
  • パラメータSはどの出力形式と整合的な文字列を書き出すかを指定します。
    • SがFANDOMの時、ファイルに書き込まれた文字列をFANDOMで出力させるとDEF文の適切な数式が表示されます。
    • SがJEKYLLの時、Jekyllで出力させるとDEF文の適切な数式が表示されるようにする予定です。(未対応)
  • パラメータMによりファイルのオープンモードを指定します。
    • MがAPPの時、ファイルの末尾に文字列を書き加えます。
    • MがTRUNCの時、ファイルを白紙化してから文字列を書き加えます。

ライブラリを導入するのが面倒という人はp進大好きbotにコードを渡して下さればファイルに書き込まれた文字列をお返しします。

3.8. C++への翻訳

\(\mathbb{Q}_p\)のソースコードCがEXISTSとUEXISTSとUNIQUEとMINとMAXを含まない時、CをC++のコードに翻訳することも可能です。

まず導入の章で述べた通常のコンパイル&リンク手順において、以下のようにインクルード対象を変更します。

  1. WindowsユーザーかつGCCユーザーはRP/libqp.hppの代わりにRP/libqpc++.hppをインクルードする。
  2. 非Windowsユーザーまたは非GCCユーザーはRP/cpp/Mathematics/Function/Computable/a.hppの代わりにRP/cpp/Mathematics/Function/Computable/Library/cpp/a.hppをインクルードする。

そしてコンマを含まない文字列FとオープンモードMに対しCONVERT_CPP(F,M); int main(){C; return 0;}というコードをコンパイル&リンクをして生成された実行ファイルを実行すると、カレントディレクトリからの相対パスがFのファイルにMに対応するオープンモードでCのC++への翻訳が書き込まれます。

書き込まれた翻訳をcとして、c内のコメントアウトされた指示(インクルードするライブラリの相対パスの変更および関数の定義文内の変数の宣言)に従ってcを 手動で変更し、cの後に改行を入れてそれより後にint main()の定義を記述することで、int main()内でCで定義された演算子や糖衣表現を使うことが出来ます。それを通常のC++のソースコードとしてコンパイル&リンクし、実行ファイルを実行することでint main()内に現れるCで定義された演算子や糖衣表現を実際に計算することができます。

ただし通常の手順でコンパイルできないような文字列でもこの操作でコンパイルできることがあります。そのためまずは通常の手順でコンパイルすることをおすすめします。またこの手順で実行する場合の挙動は、\(\mathbb{Q}_p\)に課されている実行時の制約のうち異常終了に関する制約が変更となります。詳しくは実行の章をご覧下さい。

4.実行

冒頭で述べましたように、\(\mathbb{Q}_p\)における演算子のDEF文を直接実行する機能を実装する予定は今のところないのですが、ひとまず「どのように実行されるか」は許容される文字列の分類名とそれらの定義においてカッコ書きで説明した部分を参考にして推測して下さい。C++へ翻訳した場合の実行はその規格に沿っていますので、C++へ翻訳することができるソースコードに関しては実質実行をすることが可能となります。

演算子のDEF文を実行する際の大雑把な流れは、実行中に何らかの演算子が呼ばれる時、その演算子のDEF文を更に実行する、というものです。C++による拡張を行った\(\mathbb{Q}_p\)の実行方法は、C++の関数が呼び出された箇所はC++の規格に従って実行し、そうでない箇所は通常の\(\mathbb{Q}_p\)の規格に従って実行します。

\(\mathbb{Q}_p\)における演算子のDEF文に対応する部分再帰関数の定義域は、そのDEF文の実行が正常終了するような入力全体の集合です。すなわち、それは無限ループを起こしてしまう入力および異常終了を起こしてしまう入力全体の集合の補集合となります。無限ループは演算子の自己再帰または相互再帰が停止しなかったり、無限ループする演算子の呼び出しを行ったりすることで生じます。

ここで注意が必要なのは、\(\mathbb{Q}_p\)における関係は(他のプログラミング言語における戻り値の型がboolの関数と同様)必ずしも全域とは限らないことです。関係rが全域でない時、見た目には完全な場合分けである「r(x)ならば~である。r(x)でないならば~である。」という場合分けが完全ではなくなります。何故なら「r(x)である」という条件は「r(x)の計算が停止しその出力が\(1\)である」ということに他ならない一方、「r(x)でない」という文の実装は「! r(x)である」となりすなわち「r(x)の計算が停止しその出力が\(0\)である」ということになるからです。両者は同時に成立しない場合分けではありますが、r(x)の計算が停止しない場合はどちらにも判定されません。それどころか、停止性すら未知であれば「いずれどちらかに判定されるのかこのまま計算し続けるのか」すら分かりません。これは「r(x)ならば~である。r(x)でないならば~である。」という場合分けに限った話ではなく、「r(x)ならば~である」という場合分けを含むだけでも起こることですので、場合分けにはなるべく全域性の分かっている関係を使うようにしましょう。

さて、実行において表現eがある型Tに属するとは、eの型の基底型がTの基底型T0であり、かつTをT0から特殊化するまでに用いた全ての関係Rを満たすということですが、その確認にはR(e)を呼び出す必要があるため、表向きは演算子を呼び出していない型の整合性の確認においても無限ループが生じる可能性があります。上記の事情から、SPEC構成をする場合もなるべく全域性の分かっている関係を使うようにしましょう。全域でない関係を用いてSPEC構成をした場合、対応する集合は再帰的集合である保証がありません。ちなみに再帰的集合\(X\)の再帰的とは限らない部分集合\(S\)上の計算可能関数とは、\(X\)上の計算可能関数であって計算規則が\(S\)の元にしか定義されていないもののこととします。\(S\)に対応する型を引数の型に持つ演算子はこの意味で、\(S\)上の計算可能関数に対応します。\(S\)が再帰的であろうとなかろうと、いわゆる計算不可能関数のようなものは作れないわけです。

なお、無限ループを起こしてしまう場合は実行時にエラーを出せません。一方で異常終了を起こしてしまった時は適切なエラーを標準出力する仕様を想定しています。例えばあえて「もし実行されたら規格に反してしまう命令行」をDEF文に仕込んでおきエラーの有無を確認することで、意図通りの処理が行われているかのデバッグが可能になります。ただしC++における例外処理と違い、そのような規格違反の処理は実行次第異常終了となります。以下に異常終了の条件を説明します。

4.1. EXIT文

DEF文の実行においてEXIT文が実行された場合は異常終了となります。

例えばbool型の表現bに対するIF(b,EXIT)という命令行が型intのDEF文に含まれることは許容されますが、この文が実行される時にbがtrueとして評価されることが想定されていないという意思表示になります。例えば万が一bがtrueになる場合はコードにミスがあるのでエラーを出したい時や、bがtrueになるような引数は演算子の定義域に含まないことを明示したい時にそのような文を使いましょう。

ただしC++への翻訳時はこの異常終了を無視し、代わりに例外をThrowします。

4.2. 自由変数の使用

変数xがDEF文\(D\)の実行において最初に参照される時、実行中の命令行をLと置くと、以下の2条件のいずれかを満たさなければ異常終了となります:

  1. xは\(D\)の定める演算子の引数の成分である。
  2. LはxのPUT文である。

要するに引数の成分であるかPUT文を一度実行した変数しか参照してはいけません。それは未定義な変数の使用となります。

ただしC++への翻訳時はこの異常終了を無視します。

4.3. 変数の書き換え

変数xがDEF文\(D\)の実行において参照される時、実行中の命令行がxのPUT文であるならば、以下の3条件の両方を満たさなければ異常終了となります:

  1. xは\(D\)の定める演算子の引数の成分でない。
  2. これ以前にxは一度も参照されていない。
  3. xの型をTと置くと、xを定める表現がTに属する。

要するにPUT文を実行できるのは、引数の成分でない変数を最初に参照した時に限り、かつxを定める表現がxの型と適合する場合です。例えば引数をPUT文で書き換えたり、既にPUT文を実行して束縛された変数を再度PUT文で書き換えたりしてはいけません。従って\(\mathbb{Q}_p\)における変数は一般のプログラミング言語における変数と違い、引数の成分でない限りは実質定数として振る舞うということになります。これは通常の数学における束縛変数の扱いと似せた仕様です。

ただしC++への翻訳時はこの異常終了を無視します。

4.4. 未定義な作用素の使用

\(\mathbb{Q}_p\)で許容される文字列C内のDEF文の実行において作用素gが参照される時、以下の2条件の両方を満たさなければ異常終了となります:

  1. CがgのDEF文を含む。
  2. gの引数の型をTsと置くと、gに代入された表現リストがそれぞれ対応するTsの各成分に属する。

要するに未定義の作用素を参照してはいけません。実行中のDEF文自身がgのDEF文であっても良いので、自分自身を呼び出す再帰や相互再帰は可能となります。ただし無限ループの有無は判定できません。

ただしC++への翻訳時は1.に該当する未定義な作用素の評価を含むソースコードに対しリンクエラーが起こるためそもそも実行できず、また2.に該当する未定義な作用素の評価を行う場合の異常終了を無視します。

4.5. DEF文末への到達

DEF文の実行においてDEF文末へ到達した場合は異常終了となります。

ただしC++への翻訳時はこの異常終了を無視するか、または未定義動作を起こします。